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