@nimblebrain/synapse 0.15.0 → 0.17.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 (59) hide show
  1. package/README.md +61 -111
  2. package/dist/{chunk-SN7YXLAV.cjs → chunk-EHLQWCYV.cjs} +6 -6
  3. package/dist/{chunk-SN7YXLAV.cjs.map → chunk-EHLQWCYV.cjs.map} +1 -1
  4. package/dist/{chunk-ZMNYUGSF.cjs → chunk-FDMRJZKT.cjs} +576 -662
  5. package/dist/chunk-FDMRJZKT.cjs.map +1 -0
  6. package/dist/{chunk-E7OOOYVR.js → chunk-SRQNQ26I.js} +3 -3
  7. package/dist/{chunk-E7OOOYVR.js.map → chunk-SRQNQ26I.js.map} +1 -1
  8. package/dist/{chunk-RYWW6EHB.cjs → chunk-TXJMGQYF.cjs} +3 -5
  9. package/dist/chunk-TXJMGQYF.cjs.map +1 -0
  10. package/dist/{chunk-J5ICAHK2.js → chunk-UPDBDXQB.js} +4 -5
  11. package/dist/chunk-UPDBDXQB.js.map +1 -0
  12. package/dist/{chunk-GNTQJSQX.js → chunk-VYR6V47N.js} +573 -663
  13. package/dist/chunk-VYR6V47N.js.map +1 -0
  14. package/dist/codegen/cli.cjs +1 -1
  15. package/dist/codegen/cli.js +1 -1
  16. package/dist/codegen/index.d.cts +1 -1
  17. package/dist/codegen/index.d.ts +1 -1
  18. package/dist/connect.iife.global.js +28 -28
  19. package/dist/{detect-C_qmYsh9.d.cts → detect-BHYg26_d.d.cts} +1 -1
  20. package/dist/{detect-Dfd0upoz.d.ts → detect-Bf8Q_0dK.d.ts} +1 -1
  21. package/dist/host/index.cjs +14 -14
  22. package/dist/host/index.d.cts +3 -3
  23. package/dist/host/index.d.ts +3 -3
  24. package/dist/host/index.js +2 -2
  25. package/dist/index.cjs +28 -87
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +58 -26
  28. package/dist/index.d.ts +58 -26
  29. package/dist/index.js +3 -81
  30. package/dist/index.js.map +1 -1
  31. package/dist/react/index.cjs +81 -140
  32. package/dist/react/index.cjs.map +1 -1
  33. package/dist/react/index.d.cts +66 -82
  34. package/dist/react/index.d.ts +66 -82
  35. package/dist/react/index.js +82 -136
  36. package/dist/react/index.js.map +1 -1
  37. package/dist/{server-ERRDGEZ7.js → server-5N76YCWC.js} +3 -3
  38. package/dist/{server-ERRDGEZ7.js.map → server-5N76YCWC.js.map} +1 -1
  39. package/dist/{server-P54IYX2G.cjs → server-QIYIJHD5.cjs} +3 -3
  40. package/dist/{server-P54IYX2G.cjs.map → server-QIYIJHD5.cjs.map} +1 -1
  41. package/dist/synapse-runtime.iife.global.js +28 -28
  42. package/dist/synapse-ui.iife.global.js +2 -2
  43. package/dist/{types-BSZasM8q.d.cts → types-uEO4VFJ2.d.cts} +87 -166
  44. package/dist/{types-BSZasM8q.d.ts → types-uEO4VFJ2.d.ts} +87 -166
  45. package/dist/ui/index.cjs +403 -91
  46. package/dist/ui/index.cjs.map +1 -1
  47. package/dist/ui/index.d.cts +115 -3
  48. package/dist/ui/index.d.ts +115 -3
  49. package/dist/ui/index.js +401 -93
  50. package/dist/ui/index.js.map +1 -1
  51. package/dist/vite/index.cjs +1 -1
  52. package/dist/vite/index.cjs.map +1 -1
  53. package/dist/vite/index.js +1 -1
  54. package/dist/vite/index.js.map +1 -1
  55. package/package.json +1 -1
  56. package/dist/chunk-GNTQJSQX.js.map +0 -1
  57. package/dist/chunk-J5ICAHK2.js.map +0 -1
  58. package/dist/chunk-RYWW6EHB.cjs.map +0 -1
  59. package/dist/chunk-ZMNYUGSF.cjs.map +0 -1
@@ -1,446 +1,66 @@
1
- import { extractFontFaces, applyTheme, foldFontFaces, detectHost, extractTheme, fontFacesKey } from './chunk-J5ICAHK2.js';
2
- import { INITIALIZE_METHOD, LATEST_PROTOCOL_VERSION, HOST_CONTEXT_CHANGED_METHOD, INITIALIZED_METHOD, MESSAGE_METHOD, OPEN_LINK_METHOD, TOOL_RESULT_METHOD, RESOURCE_TEARDOWN_METHOD, TOOL_CANCELLED_METHOD, TOOL_INPUT_PARTIAL_METHOD, TOOL_INPUT_METHOD } from '@modelcontextprotocol/ext-apps';
1
+ import { detectHost, foldFontFaces, applyTheme, fontFacesKey, extractTheme } from './chunk-UPDBDXQB.js';
2
+ import { INITIALIZE_METHOD, LATEST_PROTOCOL_VERSION, HOST_CONTEXT_CHANGED_METHOD, INITIALIZED_METHOD, MESSAGE_METHOD, OPEN_LINK_METHOD, RESOURCE_TEARDOWN_METHOD, TOOL_CANCELLED_METHOD, TOOL_INPUT_PARTIAL_METHOD, TOOL_INPUT_METHOD, TOOL_RESULT_METHOD } from '@modelcontextprotocol/ext-apps';
3
3
 
4
- // src/content-parser.ts
5
- function parseToolResultParams(params) {
6
- const raw = params ?? {};
7
- const structuredContent = raw.structuredContent ?? null;
8
- if (structuredContent != null) {
9
- return { content: structuredContent, structuredContent, raw };
10
- }
11
- const rawContent = raw.content;
12
- if (Array.isArray(rawContent)) {
13
- const texts = rawContent.filter(
14
- (block) => block != null && typeof block === "object" && block.type === "text" && typeof block.text === "string"
15
- ).map((block) => block.text);
16
- if (texts.length > 0) {
17
- const joined = texts.join("");
18
- try {
19
- return { content: JSON.parse(joined), structuredContent: null, raw };
20
- } catch {
21
- return { content: joined, structuredContent: null, raw };
22
- }
23
- }
24
- return { content: rawContent, structuredContent: null, raw };
25
- }
26
- if (typeof rawContent === "string") {
27
- try {
28
- return { content: JSON.parse(rawContent), structuredContent: null, raw };
29
- } catch {
30
- return { content: rawContent, structuredContent: null, raw };
31
- }
32
- }
33
- return { content: rawContent ?? null, structuredContent: null, raw };
34
- }
35
- var EVENT_MAP = {
36
- "tool-result": TOOL_RESULT_METHOD,
37
- "tool-input": TOOL_INPUT_METHOD,
38
- "tool-input-partial": TOOL_INPUT_PARTIAL_METHOD,
39
- "tool-cancelled": TOOL_CANCELLED_METHOD,
40
- "theme-changed": HOST_CONTEXT_CHANGED_METHOD,
41
- teardown: RESOURCE_TEARDOWN_METHOD
42
- };
43
- function resolveEventMethod(name) {
44
- return EVENT_MAP[name] ?? name;
45
- }
46
-
47
- // src/resize.ts
48
- function createResizer(send, autoResize) {
49
- let destroyed = false;
50
- let observer = null;
51
- let rafId = null;
52
- function measureAndSend() {
53
- if (destroyed) return;
54
- const width = document.body.scrollWidth;
55
- const height = document.body.scrollHeight;
56
- send("ui/notifications/size-changed", { width, height });
57
- }
58
- function resize(width, height) {
59
- if (destroyed) return;
60
- if (width !== void 0 && height !== void 0) {
61
- send("ui/notifications/size-changed", { width, height });
62
- } else {
63
- measureAndSend();
64
- }
65
- }
66
- if (autoResize && typeof ResizeObserver !== "undefined") {
67
- observer = new ResizeObserver(() => {
68
- if (destroyed) return;
69
- if (rafId !== null) cancelAnimationFrame(rafId);
70
- rafId = requestAnimationFrame(() => {
71
- rafId = null;
72
- measureAndSend();
73
- });
74
- });
75
- observer.observe(document.body);
76
- }
77
- function destroy() {
78
- if (destroyed) return;
79
- destroyed = true;
80
- if (rafId !== null) cancelAnimationFrame(rafId);
81
- observer?.disconnect();
82
- observer = null;
83
- }
84
- return { resize, measureAndSend, destroy };
85
- }
86
-
87
- // src/result-parser.ts
88
- function parseToolResult(raw) {
89
- if (raw == null) {
90
- return { data: null, isError: false };
91
- }
92
- if (isCallToolResult(raw)) {
93
- return parseCallToolResult(raw);
94
- }
95
- return { data: raw, isError: false };
96
- }
97
- function isCallToolResult(value) {
98
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
99
- return false;
100
- }
101
- return Array.isArray(value.content);
102
- }
103
- function isTextBlock(block) {
104
- if (block === null || typeof block !== "object" || Array.isArray(block)) {
105
- return false;
106
- }
107
- const obj = block;
108
- return obj.type === "text" && typeof obj.text === "string";
109
- }
110
- function extractMeta(result) {
111
- const meta = result._meta;
112
- if (!meta || typeof meta !== "object" || Array.isArray(meta)) return void 0;
113
- return { ...meta };
114
- }
115
- function parseCallToolResult(result) {
116
- const isError = result.isError === true;
117
- const content = result.content;
118
- const meta = extractMeta(result);
119
- if (content.length === 0) {
120
- return { data: null, isError, content, ...meta && { _meta: meta } };
121
- }
122
- const firstText = content.find(isTextBlock);
123
- if (!firstText) {
124
- return { data: content, isError, content, ...meta && { _meta: meta } };
125
- }
126
- try {
127
- return { data: JSON.parse(firstText.text), isError, content, ...meta && { _meta: meta } };
128
- } catch {
129
- return { data: firstText.text, isError, content, ...meta && { _meta: meta } };
130
- }
131
- }
132
-
133
- // src/transport.ts
134
- var SynapseTransport = class {
135
- counter = 0;
136
- destroyed = false;
137
- pending = /* @__PURE__ */ new Map();
138
- handlers = /* @__PURE__ */ new Map();
139
- listener;
140
- constructor() {
141
- this.listener = (event) => this.handleMessage(event);
142
- window.addEventListener("message", this.listener);
143
- }
144
- send(method, params) {
145
- if (this.destroyed) return;
146
- const msg = {
147
- jsonrpc: "2.0",
148
- method,
149
- ...params !== void 0 && { params }
150
- };
151
- window.parent.postMessage(msg, "*");
152
- }
153
- request(method, params) {
154
- if (this.destroyed) {
155
- return Promise.reject(new Error("Transport destroyed"));
156
- }
157
- const id = `syn-${++this.counter}`;
158
- const msg = {
159
- jsonrpc: "2.0",
160
- method,
161
- id,
162
- ...params !== void 0 && { params }
163
- };
164
- return new Promise((resolve, reject) => {
165
- this.pending.set(id, { resolve, reject });
166
- window.parent.postMessage(msg, "*");
167
- });
168
- }
169
- onMessage(method, callback) {
170
- if (!this.handlers.has(method)) {
171
- this.handlers.set(method, /* @__PURE__ */ new Set());
172
- }
173
- this.handlers.get(method)?.add(callback);
174
- return () => {
175
- const set = this.handlers.get(method);
176
- if (set) {
177
- set.delete(callback);
178
- if (set.size === 0) {
179
- this.handlers.delete(method);
180
- }
181
- }
182
- };
183
- }
184
- destroy() {
185
- if (this.destroyed) return;
186
- this.destroyed = true;
187
- window.removeEventListener("message", this.listener);
188
- const error = new Error("Transport destroyed");
189
- for (const entry of this.pending.values()) {
190
- entry.reject(error);
191
- }
192
- this.pending.clear();
193
- this.handlers.clear();
194
- }
195
- handleMessage(event) {
196
- if (this.destroyed) return;
197
- const data = event.data;
198
- if (!data || data.jsonrpc !== "2.0") return;
199
- if ("id" in data && data.id && !("method" in data)) {
200
- const response = data;
201
- const entry = this.pending.get(response.id);
202
- if (!entry) return;
203
- this.pending.delete(response.id);
204
- if (response.error) {
205
- const err = new Error(response.error.message);
206
- err.code = response.error.code;
207
- err.data = response.error.data;
208
- entry.reject(err);
209
- } else {
210
- entry.resolve(response.result);
211
- }
212
- return;
213
- }
214
- if ("method" in data && !("id" in data && data.id)) {
215
- const notification = data;
216
- const set = this.handlers.get(notification.method);
217
- if (set) {
218
- for (const handler of set) {
219
- handler(notification.params);
220
- }
221
- }
222
- }
223
- }
224
- };
225
-
226
- // src/connect.ts
227
- var READ_RESOURCE_METHOD = "resources/read";
228
- async function connect(options) {
229
- const { name, version, autoResize = false } = options;
230
- const transport = new SynapseTransport();
231
- let destroyed = false;
232
- let currentTheme = { mode: "light", tokens: {} };
233
- let hostInfo = { name: "unknown", version: "unknown" };
234
- let toolInfo = null;
235
- let containerDimensions = null;
236
- const handlers = /* @__PURE__ */ new Map();
237
- const resizer = createResizer((method, params) => transport.send(method, params), autoResize);
238
- resizer.measureAndSend();
239
- const initParams = {
240
- protocolVersion: LATEST_PROTOCOL_VERSION,
241
- appInfo: { name, version },
242
- appCapabilities: {}
243
- };
244
- const result = await transport.request(
245
- INITIALIZE_METHOD,
246
- initParams
247
- );
248
- if (result) {
249
- hostInfo = {
250
- name: result.hostInfo?.name ?? "unknown",
251
- version: result.hostInfo?.version ?? "unknown"
252
- };
253
- const ctx = result.hostContext;
254
- if (ctx) {
255
- const initialFontFaces = extractFontFaces(ctx);
256
- currentTheme = {
257
- mode: ctx.theme === "dark" ? "dark" : "light",
258
- tokens: ctx.styles?.variables && typeof ctx.styles.variables === "object" ? ctx.styles.variables : {},
259
- ...initialFontFaces ? { fontFaces: initialFontFaces } : {}
260
- };
261
- if (ctx.toolInfo && typeof ctx.toolInfo === "object") {
262
- toolInfo = { tool: ctx.toolInfo.tool ?? {} };
263
- }
264
- if (ctx.containerDimensions && typeof ctx.containerDimensions === "object") {
265
- containerDimensions = ctx.containerDimensions;
266
- }
267
- applyTheme(currentTheme.mode, currentTheme.tokens, currentTheme.fontFaces);
268
- }
269
- }
270
- transport.onMessage(HOST_CONTEXT_CHANGED_METHOD, (params) => {
271
- if (destroyed || !params) return;
272
- const ctx = params;
273
- const mode = ctx.theme === "dark" ? "dark" : "light";
274
- const variables = ctx.styles?.variables;
275
- const tokens = variables && typeof variables === "object" ? variables : currentTheme.tokens;
276
- const fontFaces = foldFontFaces(currentTheme.fontFaces, ctx);
277
- currentTheme = { mode, tokens, ...fontFaces ? { fontFaces } : {} };
278
- applyTheme(mode, tokens, fontFaces);
279
- const set = handlers.get(HOST_CONTEXT_CHANGED_METHOD);
280
- if (set) {
281
- for (const handler of set) handler(currentTheme);
282
- }
283
- });
284
- const subscribedMethods = /* @__PURE__ */ new Set([HOST_CONTEXT_CHANGED_METHOD]);
285
- function ensureTransportSub(method) {
286
- if (subscribedMethods.has(method)) return;
287
- subscribedMethods.add(method);
288
- const isToolResult = method === TOOL_RESULT_METHOD;
289
- transport.onMessage(method, (params) => {
290
- if (destroyed) return;
291
- const set = handlers.get(method);
292
- if (!set) return;
293
- for (const handler of set) {
294
- if (isToolResult) {
295
- handler(parseToolResultParams(params));
296
- } else {
297
- handler(params);
298
- }
299
- }
300
- });
301
- }
302
- if (options.on) {
303
- for (const [event, handler] of Object.entries(options.on)) {
304
- if (typeof handler === "function") {
305
- const method = resolveEventMethod(event);
306
- if (!handlers.has(method)) handlers.set(method, /* @__PURE__ */ new Set());
307
- handlers.get(method)?.add(handler);
308
- ensureTransportSub(method);
309
- }
310
- }
311
- }
312
- transport.send(INITIALIZED_METHOD, {});
313
- const app = {
314
- get theme() {
315
- return { ...currentTheme };
316
- },
317
- get hostInfo() {
318
- return { ...hostInfo };
319
- },
320
- get toolInfo() {
321
- return toolInfo;
322
- },
323
- get containerDimensions() {
324
- return containerDimensions;
325
- },
326
- on(event, handler) {
327
- const method = resolveEventMethod(event);
328
- if (!handlers.has(method)) {
329
- handlers.set(method, /* @__PURE__ */ new Set());
330
- }
331
- handlers.get(method)?.add(handler);
332
- ensureTransportSub(method);
333
- return () => {
334
- const set = handlers.get(method);
335
- if (set) {
336
- set.delete(handler);
337
- if (set.size === 0) handlers.delete(method);
338
- }
339
- };
340
- },
341
- resize(width, height) {
342
- resizer.resize(width, height);
343
- },
344
- openLink(url) {
345
- if (destroyed) return;
346
- const params = { url };
347
- transport.request(OPEN_LINK_METHOD, params).catch(() => {
348
- });
349
- },
350
- updateModelContext(state, summary) {
351
- if (destroyed) return;
352
- const params = {
353
- structuredContent: state,
354
- ...summary !== void 0 && {
355
- content: [{ type: "text", text: summary }]
356
- }
357
- };
358
- transport.send("ui/update-model-context", params);
359
- },
360
- async callTool(toolName, args) {
361
- const params = {
362
- name: toolName,
363
- arguments: args ?? {}
364
- };
365
- const raw = await transport.request(
366
- "tools/call",
367
- params
368
- );
369
- return parseToolResult(raw);
370
- },
371
- async readServerResource(params) {
372
- const raw = await transport.request(
373
- READ_RESOURCE_METHOD,
374
- params
375
- );
376
- return raw;
377
- },
378
- sendMessage(text, context) {
379
- if (destroyed) return;
380
- const textBlock = {
381
- type: "text",
382
- text,
383
- ...context && { _meta: { context } }
384
- };
385
- const params = {
386
- role: "user",
387
- content: [textBlock]
388
- };
389
- transport.send(MESSAGE_METHOD, params);
390
- },
391
- destroy() {
392
- if (destroyed) return;
393
- destroyed = true;
394
- resizer.destroy();
395
- handlers.clear();
396
- transport.destroy();
397
- }
398
- };
399
- return app;
400
- }
401
-
402
- // src/keyboard.ts
403
- var KeyboardForwarder = class {
404
- listener;
405
- destroyed = false;
406
- constructor(transport, customKeys) {
407
- const config = customKeys ?? null;
408
- this.listener = (event) => {
409
- if (this.destroyed) return;
410
- if (this.shouldForward(event, config)) {
411
- event.preventDefault();
412
- transport.send("synapse/keydown", {
413
- key: event.key,
414
- ctrlKey: event.ctrlKey,
415
- metaKey: event.metaKey,
416
- shiftKey: event.shiftKey,
417
- altKey: event.altKey
418
- });
419
- }
420
- };
421
- document.addEventListener("keydown", this.listener);
4
+ // src/internals.ts
5
+ var INTERNALS = /* @__PURE__ */ new WeakMap();
6
+ function registerInternals(app, internals) {
7
+ INTERNALS.set(app, internals);
8
+ }
9
+ function internalsFor(app) {
10
+ const internals = INTERNALS.get(app);
11
+ if (!internals) {
12
+ throw new Error(
13
+ "This App did not come from this copy of @nimblebrain/synapse. Two copies of the package are installed (check for a duplicate in your lockfile, or a bundler resolving the ESM and CJS builds separately)."
14
+ );
422
15
  }
423
- destroy() {
424
- if (this.destroyed) return;
425
- this.destroyed = true;
426
- document.removeEventListener("keydown", this.listener);
16
+ return internals;
17
+ }
18
+
19
+ // src/result-parser.ts
20
+ function parseToolResult(raw) {
21
+ if (raw == null) {
22
+ return { data: null, isError: false };
427
23
  }
428
- shouldForward(event, config) {
429
- if (config && config.length === 0) return false;
430
- if (config) {
431
- return config.some(
432
- (k) => event.key.toLowerCase() === k.key.toLowerCase() && (k.ctrl === void 0 || event.ctrlKey === k.ctrl) && (k.meta === void 0 || event.metaKey === k.meta) && (k.shift === void 0 || event.shiftKey === k.shift) && (k.alt === void 0 || event.altKey === k.alt)
433
- );
434
- }
435
- if (event.key === "Escape") return true;
436
- if (event.ctrlKey || event.metaKey) {
437
- const key = event.key.toLowerCase();
438
- if (key === "c" || key === "v" || key === "x" || key === "a") return false;
439
- return true;
440
- }
24
+ if (isCallToolResult(raw)) {
25
+ return parseCallToolResult(raw);
26
+ }
27
+ return { data: raw, isError: false };
28
+ }
29
+ function isCallToolResult(value) {
30
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
441
31
  return false;
442
32
  }
443
- };
33
+ return Array.isArray(value.content);
34
+ }
35
+ function isTextBlock(block) {
36
+ if (block === null || typeof block !== "object" || Array.isArray(block)) {
37
+ return false;
38
+ }
39
+ const obj = block;
40
+ return obj.type === "text" && typeof obj.text === "string";
41
+ }
42
+ function extractMeta(result) {
43
+ const meta = result._meta;
44
+ if (!meta || typeof meta !== "object" || Array.isArray(meta)) return void 0;
45
+ return { ...meta };
46
+ }
47
+ function parseCallToolResult(result) {
48
+ const isError = result.isError === true;
49
+ const content = result.content;
50
+ const meta = extractMeta(result);
51
+ if (content.length === 0) {
52
+ return { data: null, isError, content, ...meta && { _meta: meta } };
53
+ }
54
+ const firstText = content.find(isTextBlock);
55
+ if (!firstText) {
56
+ return { data: content, isError, content, ...meta && { _meta: meta } };
57
+ }
58
+ try {
59
+ return { data: JSON.parse(firstText.text), isError, content, ...meta && { _meta: meta } };
60
+ } catch {
61
+ return { data: firstText.text, isError, content, ...meta && { _meta: meta } };
62
+ }
63
+ }
444
64
 
445
65
  // src/task-handle.ts
446
66
  var TOOLS_CALL_METHOD = "tools/call";
@@ -485,11 +105,12 @@ function createTaskStatusRouter(transport) {
485
105
  }
486
106
  };
487
107
  }
488
- async function callToolAsTask(deps, toolName, args, options) {
489
- const hostTasks = deps.getHostTasksCapability();
108
+ async function callToolAsTask(app, toolName, args, options) {
109
+ const deps = internalsFor(app);
110
+ const hostTasks = deps.hostTasksCapability;
490
111
  if (!hostTasks?.requests?.tools?.call) {
491
112
  throw new Error(
492
- "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 `synapse.callTool`."
113
+ "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`."
493
114
  );
494
115
  }
495
116
  const taskParam = {};
@@ -501,7 +122,7 @@ async function callToolAsTask(deps, toolName, args, options) {
501
122
  task: taskParam,
502
123
  ...crossServer ? { server: deps.appName } : {}
503
124
  };
504
- const raw = await deps.transport.request(
125
+ const raw = await deps.request(
505
126
  TOOLS_CALL_METHOD,
506
127
  callParams
507
128
  );
@@ -518,7 +139,7 @@ async function callToolAsTask(deps, toolName, args, options) {
518
139
  task: initialTask,
519
140
  async result() {
520
141
  const params = { taskId };
521
- const rawResult = await deps.transport.request(
142
+ const rawResult = await deps.request(
522
143
  TASKS_RESULT_METHOD,
523
144
  params
524
145
  );
@@ -527,7 +148,7 @@ async function callToolAsTask(deps, toolName, args, options) {
527
148
  },
528
149
  async refresh() {
529
150
  const params = { taskId };
530
- const raw2 = await deps.transport.request(
151
+ const raw2 = await deps.request(
531
152
  TASKS_GET_METHOD,
532
153
  params
533
154
  );
@@ -535,7 +156,7 @@ async function callToolAsTask(deps, toolName, args, options) {
535
156
  },
536
157
  async cancel() {
537
158
  const params = { taskId };
538
- const raw2 = await deps.transport.request(
159
+ const raw2 = await deps.request(
539
160
  TASKS_CANCEL_METHOD,
540
161
  params
541
162
  );
@@ -544,7 +165,7 @@ async function callToolAsTask(deps, toolName, args, options) {
544
165
  onStatus(cb) {
545
166
  const existing = localCallbacks.get(cb);
546
167
  if (existing) return existing;
547
- const wireUnsub = deps.router.subscribe(taskId, (update) => {
168
+ const wireUnsub = deps.taskRouter.subscribe(taskId, (update) => {
548
169
  const merged = {
549
170
  taskId: update.taskId,
550
171
  status: update.status,
@@ -565,42 +186,269 @@ async function callToolAsTask(deps, toolName, args, options) {
565
186
  localCallbacks.set(cb, unsub);
566
187
  return unsub;
567
188
  }
568
- };
569
- return handle;
570
- }
571
- function projectTask(raw) {
572
- return {
573
- taskId: raw.taskId,
574
- status: raw.status,
575
- ttl: raw.ttl,
576
- createdAt: raw.createdAt,
577
- lastUpdatedAt: raw.lastUpdatedAt,
578
- ...raw.pollInterval !== void 0 && { pollInterval: raw.pollInterval },
579
- ...raw.statusMessage !== void 0 && { statusMessage: raw.statusMessage }
580
- };
581
- }
189
+ };
190
+ return handle;
191
+ }
192
+ function projectTask(raw) {
193
+ return {
194
+ taskId: raw.taskId,
195
+ status: raw.status,
196
+ ttl: raw.ttl,
197
+ createdAt: raw.createdAt,
198
+ lastUpdatedAt: raw.lastUpdatedAt,
199
+ ...raw.pollInterval !== void 0 && { pollInterval: raw.pollInterval },
200
+ ...raw.statusMessage !== void 0 && { statusMessage: raw.statusMessage }
201
+ };
202
+ }
203
+
204
+ // src/content-parser.ts
205
+ function parseToolResultParams(params) {
206
+ const raw = params ?? {};
207
+ const structuredContent = raw.structuredContent ?? null;
208
+ if (structuredContent != null) {
209
+ return { content: structuredContent, structuredContent, raw };
210
+ }
211
+ const rawContent = raw.content;
212
+ if (Array.isArray(rawContent)) {
213
+ const texts = rawContent.filter(
214
+ (block) => block != null && typeof block === "object" && block.type === "text" && typeof block.text === "string"
215
+ ).map((block) => block.text);
216
+ if (texts.length > 0) {
217
+ const joined = texts.join("");
218
+ try {
219
+ return { content: JSON.parse(joined), structuredContent: null, raw };
220
+ } catch {
221
+ return { content: joined, structuredContent: null, raw };
222
+ }
223
+ }
224
+ return { content: rawContent, structuredContent: null, raw };
225
+ }
226
+ if (typeof rawContent === "string") {
227
+ try {
228
+ return { content: JSON.parse(rawContent), structuredContent: null, raw };
229
+ } catch {
230
+ return { content: rawContent, structuredContent: null, raw };
231
+ }
232
+ }
233
+ return { content: rawContent ?? null, structuredContent: null, raw };
234
+ }
235
+ var DATA_CHANGED_METHOD = "synapse/data-changed";
236
+ var ACTION_METHOD = "synapse/action";
237
+ var REQUEST_FILE_METHOD = "synapse/request-file";
238
+ var DOWNLOAD_FILE_METHOD = "synapse/download-file";
239
+ var EVENT_MAP = {
240
+ "tool-result": TOOL_RESULT_METHOD,
241
+ "tool-input": TOOL_INPUT_METHOD,
242
+ "tool-input-partial": TOOL_INPUT_PARTIAL_METHOD,
243
+ "tool-cancelled": TOOL_CANCELLED_METHOD,
244
+ teardown: RESOURCE_TEARDOWN_METHOD
245
+ };
246
+ function resolveEventMethod(name) {
247
+ return EVENT_MAP[name] ?? name;
248
+ }
249
+
250
+ // src/keyboard.ts
251
+ var KeyboardForwarder = class {
252
+ listener;
253
+ destroyed = false;
254
+ constructor(transport, customKeys) {
255
+ const config = customKeys ?? null;
256
+ this.listener = (event) => {
257
+ if (this.destroyed) return;
258
+ if (this.shouldForward(event, config)) {
259
+ event.preventDefault();
260
+ transport.send("synapse/keydown", {
261
+ key: event.key,
262
+ ctrlKey: event.ctrlKey,
263
+ metaKey: event.metaKey,
264
+ shiftKey: event.shiftKey,
265
+ altKey: event.altKey
266
+ });
267
+ }
268
+ };
269
+ document.addEventListener("keydown", this.listener);
270
+ }
271
+ destroy() {
272
+ if (this.destroyed) return;
273
+ this.destroyed = true;
274
+ document.removeEventListener("keydown", this.listener);
275
+ }
276
+ shouldForward(event, config) {
277
+ if (config && config.length === 0) return false;
278
+ if (config) {
279
+ return config.some(
280
+ (k) => event.key.toLowerCase() === k.key.toLowerCase() && (k.ctrl === void 0 || event.ctrlKey === k.ctrl) && (k.meta === void 0 || event.metaKey === k.meta) && (k.shift === void 0 || event.shiftKey === k.shift) && (k.alt === void 0 || event.altKey === k.alt)
281
+ );
282
+ }
283
+ if (event.key === "Escape") return true;
284
+ if (event.ctrlKey || event.metaKey) {
285
+ const key = event.key.toLowerCase();
286
+ if (key === "c" || key === "v" || key === "x" || key === "a") return false;
287
+ return true;
288
+ }
289
+ return false;
290
+ }
291
+ };
292
+
293
+ // src/resize.ts
294
+ function createResizer(send, autoResize) {
295
+ let destroyed = false;
296
+ let observer = null;
297
+ let rafId = null;
298
+ function measureAndSend() {
299
+ if (destroyed) return;
300
+ const width = document.body.scrollWidth;
301
+ const height = document.body.scrollHeight;
302
+ send("ui/notifications/size-changed", { width, height });
303
+ }
304
+ function resize(width, height) {
305
+ if (destroyed) return;
306
+ if (width !== void 0 && height !== void 0) {
307
+ send("ui/notifications/size-changed", { width, height });
308
+ } else {
309
+ measureAndSend();
310
+ }
311
+ }
312
+ if (autoResize && typeof ResizeObserver !== "undefined") {
313
+ observer = new ResizeObserver(() => {
314
+ if (destroyed) return;
315
+ if (rafId !== null) cancelAnimationFrame(rafId);
316
+ rafId = requestAnimationFrame(() => {
317
+ rafId = null;
318
+ measureAndSend();
319
+ });
320
+ });
321
+ observer.observe(document.body);
322
+ }
323
+ function destroy() {
324
+ if (destroyed) return;
325
+ destroyed = true;
326
+ if (rafId !== null) cancelAnimationFrame(rafId);
327
+ observer?.disconnect();
328
+ observer = null;
329
+ }
330
+ return { resize, measureAndSend, destroy };
331
+ }
332
+
333
+ // src/transport.ts
334
+ var SynapseTransport = class {
335
+ counter = 0;
336
+ destroyed = false;
337
+ pending = /* @__PURE__ */ new Map();
338
+ handlers = /* @__PURE__ */ new Map();
339
+ listener;
340
+ constructor() {
341
+ this.listener = (event) => this.handleMessage(event);
342
+ window.addEventListener("message", this.listener);
343
+ }
344
+ send(method, params) {
345
+ if (this.destroyed) return;
346
+ const msg = {
347
+ jsonrpc: "2.0",
348
+ method,
349
+ ...params !== void 0 && { params }
350
+ };
351
+ window.parent.postMessage(msg, "*");
352
+ }
353
+ request(method, params) {
354
+ if (this.destroyed) {
355
+ return Promise.reject(new Error("Transport destroyed"));
356
+ }
357
+ const id = `syn-${++this.counter}`;
358
+ const msg = {
359
+ jsonrpc: "2.0",
360
+ method,
361
+ id,
362
+ ...params !== void 0 && { params }
363
+ };
364
+ return new Promise((resolve, reject) => {
365
+ this.pending.set(id, { resolve, reject });
366
+ window.parent.postMessage(msg, "*");
367
+ });
368
+ }
369
+ onMessage(method, callback) {
370
+ if (!this.handlers.has(method)) {
371
+ this.handlers.set(method, /* @__PURE__ */ new Set());
372
+ }
373
+ this.handlers.get(method)?.add(callback);
374
+ return () => {
375
+ const set = this.handlers.get(method);
376
+ if (set) {
377
+ set.delete(callback);
378
+ if (set.size === 0) {
379
+ this.handlers.delete(method);
380
+ }
381
+ }
382
+ };
383
+ }
384
+ destroy() {
385
+ if (this.destroyed) return;
386
+ this.destroyed = true;
387
+ window.removeEventListener("message", this.listener);
388
+ const error = new Error("Transport destroyed");
389
+ for (const entry of this.pending.values()) {
390
+ entry.reject(error);
391
+ }
392
+ this.pending.clear();
393
+ this.handlers.clear();
394
+ }
395
+ handleMessage(event) {
396
+ if (this.destroyed) return;
397
+ const data = event.data;
398
+ if (!data || data.jsonrpc !== "2.0") return;
399
+ if ("id" in data && data.id && !("method" in data)) {
400
+ const response = data;
401
+ const entry = this.pending.get(response.id);
402
+ if (!entry) return;
403
+ this.pending.delete(response.id);
404
+ if (response.error) {
405
+ const err = new Error(response.error.message);
406
+ err.code = response.error.code;
407
+ err.data = response.error.data;
408
+ entry.reject(err);
409
+ } else {
410
+ entry.resolve(response.result);
411
+ }
412
+ return;
413
+ }
414
+ if ("method" in data && !("id" in data && data.id)) {
415
+ const notification = data;
416
+ const set = this.handlers.get(notification.method);
417
+ if (set) {
418
+ for (const handler of set) {
419
+ handler(notification.params);
420
+ }
421
+ }
422
+ }
423
+ }
424
+ };
582
425
 
583
- // src/core.ts
584
- var READ_RESOURCE_METHOD2 = "resources/read";
585
- function createSynapse(options) {
586
- const { name, version, internal = false, forwardKeys } = options;
426
+ // src/connect.ts
427
+ var READ_RESOURCE_METHOD = "resources/read";
428
+ var UPDATE_MODEL_CONTEXT_METHOD = "ui/update-model-context";
429
+ async function connect(options) {
430
+ const { name, version, autoResize = false, internal = false, forwardKeys } = options;
587
431
  const transport = new SynapseTransport();
588
- let hostInfo = null;
589
- let currentHostContext = {};
590
- let currentFontFaces;
591
- function syncFontFaces(ctx) {
592
- currentFontFaces = foldFontFaces(currentFontFaces, ctx);
593
- return currentFontFaces;
594
- }
595
- function resolveTheme(ctx) {
596
- const theme = extractTheme(ctx);
597
- return currentFontFaces ? { ...theme, fontFaces: currentFontFaces } : theme;
598
- }
599
- let hostTasksCapability = null;
600
432
  let destroyed = false;
601
- const taskStatusRouter = createTaskStatusRouter(transport);
602
- let stateTimer = null;
433
+ let hostContext = {};
434
+ let hostInfo = { name: "unknown", version: "unknown" };
435
+ let isNimbleBrainHost = false;
436
+ let toolInfo = null;
437
+ let containerDimensions = null;
438
+ let hostTasksCapability;
603
439
  let keyboard = null;
440
+ let fontFaces;
441
+ function resolveTheme() {
442
+ const theme = extractTheme(hostContext);
443
+ return fontFaces ? { ...theme, fontFaces } : theme;
444
+ }
445
+ const handlers = /* @__PURE__ */ new Map();
446
+ const themeCallbacks = /* @__PURE__ */ new Set();
447
+ const hostContextCallbacks = /* @__PURE__ */ new Set();
448
+ const dataCallbacks = /* @__PURE__ */ new Set();
449
+ const actionCallbacks = /* @__PURE__ */ new Set();
450
+ const resizer = createResizer((method, params) => transport.send(method, params), autoResize);
451
+ resizer.measureAndSend();
604
452
  const appCapabilities = {
605
453
  tasks: {
606
454
  cancel: {},
@@ -612,30 +460,58 @@ function createSynapse(options) {
612
460
  appInfo: { name, version },
613
461
  appCapabilities
614
462
  };
615
- const ready = transport.request(INITIALIZE_METHOD, initParams).then((result) => {
616
- hostInfo = detectHost(result);
617
- currentHostContext = result?.hostContext ?? {};
618
- const initResult = result;
619
- const rawTasks = initResult?.hostCapabilities?.tasks;
463
+ const result = await transport.request(
464
+ INITIALIZE_METHOD,
465
+ initParams
466
+ );
467
+ if (result) {
468
+ const detected = detectHost(result);
469
+ isNimbleBrainHost = detected.isNimbleBrain;
470
+ hostInfo = {
471
+ name: result.hostInfo?.name ?? "unknown",
472
+ version: result.hostInfo?.version ?? "unknown"
473
+ };
474
+ const rawTasks = result.hostCapabilities?.tasks;
620
475
  hostTasksCapability = rawTasks && typeof rawTasks === "object" && !Array.isArray(rawTasks) ? rawTasks : void 0;
621
- {
622
- const theme = extractTheme(currentHostContext);
623
- applyTheme(theme.mode, theme.tokens, syncFontFaces(currentHostContext));
476
+ const ctx = result.hostContext;
477
+ if (ctx) {
478
+ hostContext = ctx;
479
+ fontFaces = foldFontFaces(fontFaces, ctx);
480
+ if (ctx.toolInfo && typeof ctx.toolInfo === "object") {
481
+ toolInfo = { tool: ctx.toolInfo.tool ?? {} };
482
+ }
483
+ if (ctx.containerDimensions && typeof ctx.containerDimensions === "object") {
484
+ containerDimensions = ctx.containerDimensions;
485
+ }
486
+ const theme = resolveTheme();
487
+ applyTheme(theme.mode, theme.tokens, theme.fontFaces);
488
+ }
489
+ }
490
+ if (forwardKeys && isNimbleBrainHost) {
491
+ keyboard = new KeyboardForwarder(transport, forwardKeys === true ? void 0 : forwardKeys);
492
+ }
493
+ function fanOutRaw(method, params) {
494
+ const set = handlers.get(method);
495
+ if (!set) return;
496
+ for (const handler of set) handler(params);
497
+ }
498
+ transport.onMessage(HOST_CONTEXT_CHANGED_METHOD, (params) => {
499
+ if (destroyed) return;
500
+ const ctx = params ?? {};
501
+ const prevTheme = resolveTheme();
502
+ hostContext = { ...hostContext, ...ctx };
503
+ fontFaces = foldFontFaces(fontFaces, ctx);
504
+ const nextTheme = resolveTheme();
505
+ applyTheme(nextTheme.mode, nextTheme.tokens, nextTheme.fontFaces);
506
+ for (const cb of hostContextCallbacks) cb(hostContext);
507
+ fanOutRaw(HOST_CONTEXT_CHANGED_METHOD, params);
508
+ if (!themesEqual(prevTheme, nextTheme)) {
509
+ for (const cb of themeCallbacks) cb(nextTheme);
624
510
  }
625
- for (const cb of hostContextCallbacks) cb(currentHostContext);
626
- transport.send(INITIALIZED_METHOD, {});
627
- keyboard = new KeyboardForwarder(transport, forwardKeys);
628
- });
629
- const unsubHostContext = transport.onMessage(HOST_CONTEXT_CHANGED_METHOD, (params) => {
630
- currentHostContext = params ?? {};
631
- const theme = extractTheme(currentHostContext);
632
- applyTheme(theme.mode, theme.tokens, syncFontFaces(currentHostContext));
633
- for (const cb of hostContextCallbacks) cb(currentHostContext);
634
511
  });
635
- const hostContextCallbacks = /* @__PURE__ */ new Set();
636
- const dataCallbacks = /* @__PURE__ */ new Set();
637
- const actionCallbacks = /* @__PURE__ */ new Set();
638
- const unsubData = transport.onMessage("synapse/data-changed", (params) => {
512
+ transport.onMessage(DATA_CHANGED_METHOD, (params) => {
513
+ if (destroyed) return;
514
+ fanOutRaw(DATA_CHANGED_METHOD, params);
639
515
  if (!params) return;
640
516
  const event = {
641
517
  source: "agent",
@@ -644,119 +520,157 @@ function createSynapse(options) {
644
520
  };
645
521
  for (const cb of dataCallbacks) cb(event);
646
522
  });
647
- const unsubAction = transport.onMessage("synapse/action", (params) => {
523
+ transport.onMessage(ACTION_METHOD, (params) => {
524
+ if (destroyed) return;
525
+ fanOutRaw(ACTION_METHOD, params);
648
526
  if (!params || typeof params.type !== "string") return;
649
- const action = {
527
+ const action2 = {
650
528
  type: params.type,
651
529
  payload: params.payload ?? {},
652
530
  requiresConfirmation: params.requiresConfirmation === true,
653
531
  label: typeof params.label === "string" ? params.label : void 0
654
532
  };
655
- for (const cb of actionCallbacks) cb(action);
533
+ for (const cb of actionCallbacks) cb(action2);
656
534
  });
657
- const isNB = () => hostInfo?.isNimbleBrain === true;
658
- const synapse = {
659
- get ready() {
660
- return ready;
535
+ const subscribedMethods = /* @__PURE__ */ new Set([
536
+ HOST_CONTEXT_CHANGED_METHOD,
537
+ DATA_CHANGED_METHOD,
538
+ ACTION_METHOD
539
+ ]);
540
+ function ensureTransportSub(method) {
541
+ if (subscribedMethods.has(method)) return;
542
+ subscribedMethods.add(method);
543
+ const isToolResult = method === TOOL_RESULT_METHOD;
544
+ transport.onMessage(method, (params) => {
545
+ if (destroyed) return;
546
+ const set = handlers.get(method);
547
+ if (!set) return;
548
+ for (const handler of set) {
549
+ if (isToolResult) {
550
+ handler(parseToolResultParams(params));
551
+ } else {
552
+ handler(params);
553
+ }
554
+ }
555
+ });
556
+ }
557
+ function subscribe(event, handler) {
558
+ switch (event) {
559
+ case "theme-changed":
560
+ themeCallbacks.add(handler);
561
+ return () => {
562
+ themeCallbacks.delete(handler);
563
+ };
564
+ case "host-context-changed":
565
+ hostContextCallbacks.add(handler);
566
+ return () => {
567
+ hostContextCallbacks.delete(handler);
568
+ };
569
+ case "data-changed":
570
+ dataCallbacks.add(handler);
571
+ return () => {
572
+ dataCallbacks.delete(handler);
573
+ };
574
+ case "action":
575
+ actionCallbacks.add(handler);
576
+ return () => {
577
+ actionCallbacks.delete(handler);
578
+ };
579
+ default: {
580
+ const method = resolveEventMethod(event);
581
+ if (!handlers.has(method)) handlers.set(method, /* @__PURE__ */ new Set());
582
+ handlers.get(method)?.add(handler);
583
+ ensureTransportSub(method);
584
+ return () => {
585
+ const set = handlers.get(method);
586
+ if (set) {
587
+ set.delete(handler);
588
+ if (set.size === 0) handlers.delete(method);
589
+ }
590
+ };
591
+ }
592
+ }
593
+ }
594
+ if (options.on) {
595
+ for (const [event, handler] of Object.entries(options.on)) {
596
+ if (typeof handler === "function") subscribe(event, handler);
597
+ }
598
+ }
599
+ transport.send(INITIALIZED_METHOD, {});
600
+ const taskRouter = createTaskStatusRouter(transport);
601
+ const app = {
602
+ get theme() {
603
+ return resolveTheme();
604
+ },
605
+ get hostInfo() {
606
+ return { ...hostInfo };
607
+ },
608
+ get hostContext() {
609
+ return hostContext;
661
610
  },
662
611
  get isNimbleBrainHost() {
663
- return isNB();
612
+ return isNimbleBrainHost;
613
+ },
614
+ get toolInfo() {
615
+ return toolInfo;
616
+ },
617
+ get containerDimensions() {
618
+ return containerDimensions;
664
619
  },
665
620
  get destroyed() {
666
621
  return destroyed;
667
622
  },
668
- async callTool(toolName, args) {
623
+ get supportsTasks() {
624
+ return hostTasksCapability?.requests?.tools?.call !== void 0;
625
+ },
626
+ on(event, handler) {
627
+ return subscribe(event, handler);
628
+ },
629
+ resize(width, height) {
630
+ resizer.resize(width, height);
631
+ },
632
+ openLink(url) {
633
+ if (destroyed) return;
634
+ const params = { url };
635
+ transport.request(OPEN_LINK_METHOD, params).catch(() => {
636
+ window.open(url, "_blank", "noopener");
637
+ });
638
+ },
639
+ updateModelContext(state, summary) {
640
+ if (destroyed) return;
641
+ const params = {
642
+ structuredContent: state,
643
+ ...summary !== void 0 && {
644
+ content: [{ type: "text", text: summary }]
645
+ }
646
+ };
647
+ transport.send(UPDATE_MODEL_CONTEXT_METHOD, params);
648
+ },
649
+ async callTool(toolName, args, callOptions) {
650
+ const server = callOptions?.server ?? (internal ? name : void 0);
669
651
  const params = {
670
652
  name: toolName,
671
- arguments: args ?? {}
653
+ arguments: args ?? {},
654
+ ...server !== void 0 && { server }
672
655
  };
673
- if (internal) {
674
- params.server = name;
675
- }
676
- const raw = await transport.request("tools/call", params);
656
+ const raw = await transport.request(TOOLS_CALL_METHOD, params);
677
657
  return parseToolResult(raw);
678
658
  },
679
- async callToolAsTask(toolName, args, options2) {
680
- return callToolAsTask(
681
- {
682
- transport,
683
- router: taskStatusRouter,
684
- getHostTasksCapability: () => hostTasksCapability,
685
- appName: name,
686
- internalApp: internal
687
- },
688
- toolName,
689
- args,
690
- options2
691
- );
692
- },
693
- async readResource(uri) {
694
- const params = { uri };
659
+ async readServerResource(params) {
695
660
  const raw = await transport.request(
696
- READ_RESOURCE_METHOD2,
661
+ READ_RESOURCE_METHOD,
697
662
  params
698
663
  );
699
664
  return raw;
700
665
  },
701
- onDataChanged(callback) {
702
- dataCallbacks.add(callback);
703
- return () => {
704
- dataCallbacks.delete(callback);
705
- };
706
- },
707
- onAction(callback) {
708
- actionCallbacks.add(callback);
709
- return () => {
710
- actionCallbacks.delete(callback);
711
- };
712
- },
713
- getHostContext() {
714
- return currentHostContext;
715
- },
716
- onHostContextChanged(callback) {
717
- hostContextCallbacks.add(callback);
718
- return () => {
719
- hostContextCallbacks.delete(callback);
720
- };
721
- },
722
- getTheme() {
723
- return resolveTheme(currentHostContext);
724
- },
725
- // Selector over `onHostContextChanged`: only fires when the *derived*
726
- // theme actually changes, so theme subscribers don't see spurious
727
- // updates when other host-context fields (e.g. workspace) change.
728
- //
729
- // Subscriber timing matters:
730
- // - Subscribed BEFORE handshake: `prev = null` sentinel. The first
731
- // fire (the handshake dispatch) always invokes the callback,
732
- // even if the host's theme happens to derive to the default.
733
- // Otherwise consumers using `onThemeChanged` as their init
734
- // signal would silently miss it.
735
- // - Subscribed AFTER handshake: `prev` is pre-seeded with the
736
- // current derived theme, so a workspace-only `host-context-changed`
737
- // notification correctly filters as a no-op.
738
- onThemeChanged(callback) {
739
- let prev = hostInfo !== null ? resolveTheme(currentHostContext) : null;
740
- const wrapped = (ctx) => {
741
- const next = resolveTheme(ctx);
742
- if (prev !== null && themesEqual(prev, next)) return;
743
- prev = next;
744
- callback(next);
745
- };
746
- hostContextCallbacks.add(wrapped);
747
- return () => {
748
- hostContextCallbacks.delete(wrapped);
749
- };
750
- },
751
- action(action, params) {
752
- if (!isNB()) return;
753
- transport.send("synapse/action", { action, ...params });
754
- },
755
- chat(message, context) {
666
+ sendMessage(text, context) {
667
+ if (destroyed) return;
756
668
  const textBlock = {
757
669
  type: "text",
758
- text: message,
759
- ...isNB() && context && { _meta: { context } }
670
+ text,
671
+ // `_meta.context` is a NimbleBrain convention; other hosts ignore it,
672
+ // but there is no reason to spend the bytes off one.
673
+ ...isNimbleBrainHost && context && { _meta: { context } }
760
674
  };
761
675
  const params = {
762
676
  role: "user",
@@ -764,80 +678,87 @@ function createSynapse(options) {
764
678
  };
765
679
  transport.send(MESSAGE_METHOD, params);
766
680
  },
767
- setVisibleState(state, summary) {
768
- if (stateTimer) clearTimeout(stateTimer);
769
- stateTimer = setTimeout(() => {
770
- const params = {
771
- structuredContent: state,
772
- ...summary !== void 0 && {
773
- content: [{ type: "text", text: summary }]
774
- }
775
- };
776
- transport.send("ui/update-model-context", params);
777
- stateTimer = null;
778
- }, 250);
779
- },
780
- downloadFile(filename, content, mimeType) {
781
- const resolvedMime = mimeType || (content instanceof Blob ? content.type : "") || "application/octet-stream";
782
- const blob = content instanceof Blob ? content : new Blob([content], { type: resolvedMime });
783
- transport.send("synapse/download-file", {
784
- data: blob,
785
- filename,
786
- mimeType: resolvedMime
787
- });
788
- },
789
- openLink(url) {
790
- const params = { url };
791
- transport.request(OPEN_LINK_METHOD, params).catch(() => {
792
- window.open(url, "_blank", "noopener");
793
- });
794
- },
795
- async pickFile(options2) {
796
- if (!isNB()) {
797
- throw new Error("pickFile is not supported in this host");
798
- }
799
- const result = await requestFile(transport, options2, false);
800
- if (result === null) return null;
801
- return Array.isArray(result) ? result[0] ?? null : result;
802
- },
803
- async pickFiles(options2) {
804
- if (!isNB()) {
805
- throw new Error("pickFiles is not supported in this host");
806
- }
807
- const result = await requestFile(transport, options2, true);
808
- if (result === null) return [];
809
- return Array.isArray(result) ? result : [result];
810
- },
811
- _onMessage(method, callback) {
812
- return transport.onMessage(method, callback);
813
- },
814
- _request(method, params) {
815
- return transport.request(method, params);
816
- },
817
- get _hostTasksCapability() {
818
- return hostTasksCapability;
819
- },
820
681
  destroy() {
821
682
  if (destroyed) return;
822
683
  destroyed = true;
823
- if (stateTimer) clearTimeout(stateTimer);
824
684
  keyboard?.destroy();
825
- unsubHostContext();
826
- unsubData();
827
- unsubAction();
828
- taskStatusRouter.dispose();
685
+ resizer.destroy();
686
+ taskRouter.dispose();
687
+ handlers.clear();
688
+ themeCallbacks.clear();
829
689
  hostContextCallbacks.clear();
830
690
  dataCallbacks.clear();
831
691
  actionCallbacks.clear();
832
692
  transport.destroy();
833
693
  }
834
694
  };
835
- return synapse;
695
+ registerInternals(app, {
696
+ send(method, params) {
697
+ if (destroyed) return;
698
+ transport.send(method, params);
699
+ },
700
+ request(method, params) {
701
+ return transport.request(method, params);
702
+ },
703
+ onMessage(method, handler) {
704
+ return transport.onMessage(method, handler);
705
+ },
706
+ taskRouter,
707
+ get hostTasksCapability() {
708
+ return hostTasksCapability;
709
+ },
710
+ appName: name,
711
+ internalApp: internal
712
+ });
713
+ return app;
714
+ }
715
+ function themesEqual(a, b) {
716
+ if (a.mode !== b.mode) return false;
717
+ if (fontFacesKey(a.fontFaces) !== fontFacesKey(b.fontFaces)) return false;
718
+ const aKeys = Object.keys(a.tokens);
719
+ const bKeys = Object.keys(b.tokens);
720
+ if (aKeys.length !== bKeys.length) return false;
721
+ for (const k of aKeys) {
722
+ if (a.tokens[k] !== b.tokens[k]) return false;
723
+ }
724
+ return true;
725
+ }
726
+
727
+ // src/extensions.ts
728
+ var DEFAULT_MAX_FILE_SIZE = 26214400;
729
+ function action(app, name, params) {
730
+ if (!app.isNimbleBrainHost) return;
731
+ internalsFor(app).send(ACTION_METHOD, { action: name, ...params });
836
732
  }
837
- async function requestFile(transport, options, multiple) {
838
- const result = await transport.request("synapse/request-file", {
733
+ function downloadFile(app, filename, content, mimeType) {
734
+ const resolvedMime = mimeType || (content instanceof Blob ? content.type : "") || "application/octet-stream";
735
+ const blob = content instanceof Blob ? content : new Blob([content], { type: resolvedMime });
736
+ internalsFor(app).send(DOWNLOAD_FILE_METHOD, {
737
+ data: blob,
738
+ filename,
739
+ mimeType: resolvedMime
740
+ });
741
+ }
742
+ async function pickFile(app, options) {
743
+ if (!app.isNimbleBrainHost) {
744
+ throw new Error("pickFile is not supported in this host");
745
+ }
746
+ const result = await requestFile(app, options, false);
747
+ if (result === null) return null;
748
+ return Array.isArray(result) ? result[0] ?? null : result;
749
+ }
750
+ async function pickFiles(app, options) {
751
+ if (!app.isNimbleBrainHost) {
752
+ throw new Error("pickFiles is not supported in this host");
753
+ }
754
+ const result = await requestFile(app, options, true);
755
+ if (result === null) return [];
756
+ return Array.isArray(result) ? result : [result];
757
+ }
758
+ async function requestFile(app, options, multiple) {
759
+ const result = await internalsFor(app).request(REQUEST_FILE_METHOD, {
839
760
  accept: options?.accept,
840
- maxSize: options?.maxSize ?? 26214400,
761
+ maxSize: options?.maxSize ?? DEFAULT_MAX_FILE_SIZE,
841
762
  multiple
842
763
  });
843
764
  if (result == null) return null;
@@ -854,18 +775,7 @@ function validateFileResult(value) {
854
775
  }
855
776
  return value;
856
777
  }
857
- function themesEqual(a, b) {
858
- if (a.mode !== b.mode || a.primaryColor !== b.primaryColor) return false;
859
- if (fontFacesKey(a.fontFaces) !== fontFacesKey(b.fontFaces)) return false;
860
- const aKeys = Object.keys(a.tokens);
861
- const bKeys = Object.keys(b.tokens);
862
- if (aKeys.length !== bKeys.length) return false;
863
- for (const k of aKeys) {
864
- if (a.tokens[k] !== b.tokens[k]) return false;
865
- }
866
- return true;
867
- }
868
778
 
869
- export { connect, createSynapse };
870
- //# sourceMappingURL=chunk-GNTQJSQX.js.map
871
- //# sourceMappingURL=chunk-GNTQJSQX.js.map
779
+ export { action, callToolAsTask, connect, downloadFile, pickFile, pickFiles };
780
+ //# sourceMappingURL=chunk-VYR6V47N.js.map
781
+ //# sourceMappingURL=chunk-VYR6V47N.js.map