@nimblebrain/synapse 0.2.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +185 -53
- package/dist/chunk-54YIV4ZL.cjs +656 -0
- package/dist/chunk-54YIV4ZL.cjs.map +1 -0
- package/dist/chunk-LYJHA5B2.js +653 -0
- package/dist/chunk-LYJHA5B2.js.map +1 -0
- package/dist/codegen/index.d.cts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/connect.iife.global.js +78 -0
- package/dist/index.cjs +6 -2
- package/dist/index.d.cts +11 -3
- package/dist/index.d.ts +11 -3
- package/dist/index.js +1 -1
- package/dist/react/index.cjs +96 -8
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +15 -4
- package/dist/react/index.d.ts +15 -4
- package/dist/react/index.js +92 -10
- package/dist/react/index.js.map +1 -1
- package/dist/synapse-runtime.iife.global.js +78 -1
- package/dist/{types-CG7zrCn-.d.cts → types-Dj3Wv4hW.d.cts} +55 -5
- package/dist/{types-CG7zrCn-.d.ts → types-Dj3Wv4hW.d.ts} +55 -5
- package/package.json +4 -1
- package/dist/chunk-MQNKIR7K.cjs +0 -406
- package/dist/chunk-MQNKIR7K.cjs.map +0 -1
- package/dist/chunk-QY4IBJKV.js +0 -404
- package/dist/chunk-QY4IBJKV.js.map +0 -1
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var extApps = require('@modelcontextprotocol/ext-apps');
|
|
4
|
+
|
|
5
|
+
// src/connect.ts
|
|
6
|
+
|
|
7
|
+
// src/content-parser.ts
|
|
8
|
+
function parseToolResultParams(params) {
|
|
9
|
+
const raw = params ?? {};
|
|
10
|
+
const structuredContent = raw.structuredContent ?? null;
|
|
11
|
+
if (structuredContent != null) {
|
|
12
|
+
return { content: structuredContent, structuredContent, raw };
|
|
13
|
+
}
|
|
14
|
+
const rawContent = raw.content;
|
|
15
|
+
if (Array.isArray(rawContent)) {
|
|
16
|
+
const texts = rawContent.filter(
|
|
17
|
+
(block) => block != null && typeof block === "object" && block.type === "text" && typeof block.text === "string"
|
|
18
|
+
).map((block) => block.text);
|
|
19
|
+
if (texts.length > 0) {
|
|
20
|
+
const joined = texts.join("");
|
|
21
|
+
try {
|
|
22
|
+
return { content: JSON.parse(joined), structuredContent: null, raw };
|
|
23
|
+
} catch {
|
|
24
|
+
return { content: joined, structuredContent: null, raw };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return { content: rawContent, structuredContent: null, raw };
|
|
28
|
+
}
|
|
29
|
+
if (typeof rawContent === "string") {
|
|
30
|
+
try {
|
|
31
|
+
return { content: JSON.parse(rawContent), structuredContent: null, raw };
|
|
32
|
+
} catch {
|
|
33
|
+
return { content: rawContent, structuredContent: null, raw };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { content: rawContent ?? null, structuredContent: null, raw };
|
|
37
|
+
}
|
|
38
|
+
var EVENT_MAP = {
|
|
39
|
+
"tool-result": extApps.TOOL_RESULT_METHOD,
|
|
40
|
+
"tool-input": extApps.TOOL_INPUT_METHOD,
|
|
41
|
+
"tool-input-partial": extApps.TOOL_INPUT_PARTIAL_METHOD,
|
|
42
|
+
"tool-cancelled": extApps.TOOL_CANCELLED_METHOD,
|
|
43
|
+
"theme-changed": extApps.HOST_CONTEXT_CHANGED_METHOD,
|
|
44
|
+
teardown: extApps.RESOURCE_TEARDOWN_METHOD
|
|
45
|
+
};
|
|
46
|
+
function resolveEventMethod(name) {
|
|
47
|
+
return EVENT_MAP[name] ?? name;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/resize.ts
|
|
51
|
+
function createResizer(send, autoResize) {
|
|
52
|
+
let destroyed = false;
|
|
53
|
+
let observer = null;
|
|
54
|
+
let rafId = null;
|
|
55
|
+
function measureAndSend() {
|
|
56
|
+
if (destroyed) return;
|
|
57
|
+
const width = document.body.scrollWidth;
|
|
58
|
+
const height = document.body.scrollHeight;
|
|
59
|
+
send("ui/notifications/size-changed", { width, height });
|
|
60
|
+
}
|
|
61
|
+
function resize(width, height) {
|
|
62
|
+
if (destroyed) return;
|
|
63
|
+
if (width !== void 0 && height !== void 0) {
|
|
64
|
+
send("ui/notifications/size-changed", { width, height });
|
|
65
|
+
} else {
|
|
66
|
+
measureAndSend();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (autoResize && typeof ResizeObserver !== "undefined") {
|
|
70
|
+
observer = new ResizeObserver(() => {
|
|
71
|
+
if (destroyed) return;
|
|
72
|
+
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
73
|
+
rafId = requestAnimationFrame(() => {
|
|
74
|
+
rafId = null;
|
|
75
|
+
measureAndSend();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
observer.observe(document.body);
|
|
79
|
+
}
|
|
80
|
+
function destroy() {
|
|
81
|
+
if (destroyed) return;
|
|
82
|
+
destroyed = true;
|
|
83
|
+
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
84
|
+
observer?.disconnect();
|
|
85
|
+
observer = null;
|
|
86
|
+
}
|
|
87
|
+
return { resize, measureAndSend, destroy };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/result-parser.ts
|
|
91
|
+
function parseToolResult(raw) {
|
|
92
|
+
if (raw == null) {
|
|
93
|
+
return { data: null, isError: false };
|
|
94
|
+
}
|
|
95
|
+
if (isCallToolResult(raw)) {
|
|
96
|
+
return parseCallToolResult(raw);
|
|
97
|
+
}
|
|
98
|
+
return { data: raw, isError: false };
|
|
99
|
+
}
|
|
100
|
+
function isCallToolResult(value) {
|
|
101
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return Array.isArray(value.content);
|
|
105
|
+
}
|
|
106
|
+
function isTextBlock(block) {
|
|
107
|
+
if (block === null || typeof block !== "object" || Array.isArray(block)) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
const obj = block;
|
|
111
|
+
return obj.type === "text" && typeof obj.text === "string";
|
|
112
|
+
}
|
|
113
|
+
function parseCallToolResult(result) {
|
|
114
|
+
const isError = result.isError === true;
|
|
115
|
+
const content = result.content;
|
|
116
|
+
if (content.length === 0) {
|
|
117
|
+
return { data: null, isError };
|
|
118
|
+
}
|
|
119
|
+
const firstText = content.find(isTextBlock);
|
|
120
|
+
if (!firstText) {
|
|
121
|
+
return { data: content, isError };
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
return { data: JSON.parse(firstText.text), isError };
|
|
125
|
+
} catch {
|
|
126
|
+
return { data: firstText.text, isError };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/transport.ts
|
|
131
|
+
var SynapseTransport = class {
|
|
132
|
+
counter = 0;
|
|
133
|
+
destroyed = false;
|
|
134
|
+
pending = /* @__PURE__ */ new Map();
|
|
135
|
+
handlers = /* @__PURE__ */ new Map();
|
|
136
|
+
listener;
|
|
137
|
+
constructor() {
|
|
138
|
+
this.listener = (event) => this.handleMessage(event);
|
|
139
|
+
window.addEventListener("message", this.listener);
|
|
140
|
+
}
|
|
141
|
+
send(method, params) {
|
|
142
|
+
if (this.destroyed) return;
|
|
143
|
+
const msg = {
|
|
144
|
+
jsonrpc: "2.0",
|
|
145
|
+
method,
|
|
146
|
+
...params !== void 0 && { params }
|
|
147
|
+
};
|
|
148
|
+
window.parent.postMessage(msg, "*");
|
|
149
|
+
}
|
|
150
|
+
request(method, params) {
|
|
151
|
+
if (this.destroyed) {
|
|
152
|
+
return Promise.reject(new Error("Transport destroyed"));
|
|
153
|
+
}
|
|
154
|
+
const id = `syn-${++this.counter}`;
|
|
155
|
+
const msg = {
|
|
156
|
+
jsonrpc: "2.0",
|
|
157
|
+
method,
|
|
158
|
+
id,
|
|
159
|
+
...params !== void 0 && { params }
|
|
160
|
+
};
|
|
161
|
+
return new Promise((resolve, reject) => {
|
|
162
|
+
this.pending.set(id, { resolve, reject });
|
|
163
|
+
window.parent.postMessage(msg, "*");
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
onMessage(method, callback) {
|
|
167
|
+
if (!this.handlers.has(method)) {
|
|
168
|
+
this.handlers.set(method, /* @__PURE__ */ new Set());
|
|
169
|
+
}
|
|
170
|
+
this.handlers.get(method)?.add(callback);
|
|
171
|
+
return () => {
|
|
172
|
+
const set = this.handlers.get(method);
|
|
173
|
+
if (set) {
|
|
174
|
+
set.delete(callback);
|
|
175
|
+
if (set.size === 0) {
|
|
176
|
+
this.handlers.delete(method);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
destroy() {
|
|
182
|
+
if (this.destroyed) return;
|
|
183
|
+
this.destroyed = true;
|
|
184
|
+
window.removeEventListener("message", this.listener);
|
|
185
|
+
const error = new Error("Transport destroyed");
|
|
186
|
+
for (const entry of this.pending.values()) {
|
|
187
|
+
entry.reject(error);
|
|
188
|
+
}
|
|
189
|
+
this.pending.clear();
|
|
190
|
+
this.handlers.clear();
|
|
191
|
+
}
|
|
192
|
+
handleMessage(event) {
|
|
193
|
+
if (this.destroyed) return;
|
|
194
|
+
const data = event.data;
|
|
195
|
+
if (!data || data.jsonrpc !== "2.0") return;
|
|
196
|
+
if ("id" in data && data.id && !("method" in data)) {
|
|
197
|
+
const response = data;
|
|
198
|
+
const entry = this.pending.get(response.id);
|
|
199
|
+
if (!entry) return;
|
|
200
|
+
this.pending.delete(response.id);
|
|
201
|
+
if (response.error) {
|
|
202
|
+
const err = new Error(response.error.message);
|
|
203
|
+
err.code = response.error.code;
|
|
204
|
+
err.data = response.error.data;
|
|
205
|
+
entry.reject(err);
|
|
206
|
+
} else {
|
|
207
|
+
entry.resolve(response.result);
|
|
208
|
+
}
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if ("method" in data && !("id" in data && data.id)) {
|
|
212
|
+
const notification = data;
|
|
213
|
+
const set = this.handlers.get(notification.method);
|
|
214
|
+
if (set) {
|
|
215
|
+
for (const handler of set) {
|
|
216
|
+
handler(notification.params);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// src/connect.ts
|
|
224
|
+
async function connect(options) {
|
|
225
|
+
const { name, version, autoResize = false } = options;
|
|
226
|
+
const transport = new SynapseTransport();
|
|
227
|
+
let destroyed = false;
|
|
228
|
+
let currentTheme = { mode: "light", tokens: {} };
|
|
229
|
+
let hostInfo = { name: "unknown", version: "unknown" };
|
|
230
|
+
let toolInfo = null;
|
|
231
|
+
let containerDimensions = null;
|
|
232
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
233
|
+
const resizer = createResizer((method, params) => transport.send(method, params), autoResize);
|
|
234
|
+
resizer.measureAndSend();
|
|
235
|
+
const initParams = {
|
|
236
|
+
protocolVersion: extApps.LATEST_PROTOCOL_VERSION,
|
|
237
|
+
appInfo: { name, version },
|
|
238
|
+
appCapabilities: {}
|
|
239
|
+
};
|
|
240
|
+
const result = await transport.request(
|
|
241
|
+
extApps.INITIALIZE_METHOD,
|
|
242
|
+
initParams
|
|
243
|
+
);
|
|
244
|
+
if (result) {
|
|
245
|
+
hostInfo = {
|
|
246
|
+
name: result.hostInfo?.name ?? "unknown",
|
|
247
|
+
version: result.hostInfo?.version ?? "unknown"
|
|
248
|
+
};
|
|
249
|
+
const ctx = result.hostContext;
|
|
250
|
+
if (ctx) {
|
|
251
|
+
currentTheme = {
|
|
252
|
+
mode: ctx.theme === "dark" ? "dark" : "light",
|
|
253
|
+
tokens: ctx.styles?.variables && typeof ctx.styles.variables === "object" ? ctx.styles.variables : {}
|
|
254
|
+
};
|
|
255
|
+
if (ctx.toolInfo && typeof ctx.toolInfo === "object") {
|
|
256
|
+
toolInfo = { tool: ctx.toolInfo.tool ?? {} };
|
|
257
|
+
}
|
|
258
|
+
if (ctx.containerDimensions && typeof ctx.containerDimensions === "object") {
|
|
259
|
+
containerDimensions = ctx.containerDimensions;
|
|
260
|
+
}
|
|
261
|
+
injectCssVariables(ctx.styles?.variables);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
transport.onMessage(extApps.HOST_CONTEXT_CHANGED_METHOD, (params) => {
|
|
265
|
+
if (destroyed || !params) return;
|
|
266
|
+
const ctx = params;
|
|
267
|
+
const mode = ctx.theme === "dark" ? "dark" : "light";
|
|
268
|
+
const variables = ctx.styles?.variables;
|
|
269
|
+
const tokens = variables && typeof variables === "object" ? variables : currentTheme.tokens;
|
|
270
|
+
currentTheme = { mode, tokens };
|
|
271
|
+
injectCssVariables(tokens);
|
|
272
|
+
const set = handlers.get(extApps.HOST_CONTEXT_CHANGED_METHOD);
|
|
273
|
+
if (set) {
|
|
274
|
+
for (const handler of set) handler(currentTheme);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
const subscribedMethods = /* @__PURE__ */ new Set([extApps.HOST_CONTEXT_CHANGED_METHOD]);
|
|
278
|
+
function ensureTransportSub(method) {
|
|
279
|
+
if (subscribedMethods.has(method)) return;
|
|
280
|
+
subscribedMethods.add(method);
|
|
281
|
+
const isToolResult = method === extApps.TOOL_RESULT_METHOD;
|
|
282
|
+
transport.onMessage(method, (params) => {
|
|
283
|
+
if (destroyed) return;
|
|
284
|
+
const set = handlers.get(method);
|
|
285
|
+
if (!set) return;
|
|
286
|
+
for (const handler of set) {
|
|
287
|
+
if (isToolResult) {
|
|
288
|
+
handler(parseToolResultParams(params));
|
|
289
|
+
} else {
|
|
290
|
+
handler(params);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
if (options.on) {
|
|
296
|
+
for (const [event, handler] of Object.entries(options.on)) {
|
|
297
|
+
if (typeof handler === "function") {
|
|
298
|
+
const method = resolveEventMethod(event);
|
|
299
|
+
if (!handlers.has(method)) handlers.set(method, /* @__PURE__ */ new Set());
|
|
300
|
+
handlers.get(method)?.add(handler);
|
|
301
|
+
ensureTransportSub(method);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
transport.send(extApps.INITIALIZED_METHOD, {});
|
|
306
|
+
const app = {
|
|
307
|
+
get theme() {
|
|
308
|
+
return { ...currentTheme };
|
|
309
|
+
},
|
|
310
|
+
get hostInfo() {
|
|
311
|
+
return { ...hostInfo };
|
|
312
|
+
},
|
|
313
|
+
get toolInfo() {
|
|
314
|
+
return toolInfo;
|
|
315
|
+
},
|
|
316
|
+
get containerDimensions() {
|
|
317
|
+
return containerDimensions;
|
|
318
|
+
},
|
|
319
|
+
on(event, handler) {
|
|
320
|
+
const method = resolveEventMethod(event);
|
|
321
|
+
if (!handlers.has(method)) {
|
|
322
|
+
handlers.set(method, /* @__PURE__ */ new Set());
|
|
323
|
+
}
|
|
324
|
+
handlers.get(method)?.add(handler);
|
|
325
|
+
ensureTransportSub(method);
|
|
326
|
+
return () => {
|
|
327
|
+
const set = handlers.get(method);
|
|
328
|
+
if (set) {
|
|
329
|
+
set.delete(handler);
|
|
330
|
+
if (set.size === 0) handlers.delete(method);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
},
|
|
334
|
+
resize(width, height) {
|
|
335
|
+
resizer.resize(width, height);
|
|
336
|
+
},
|
|
337
|
+
openLink(url) {
|
|
338
|
+
if (destroyed) return;
|
|
339
|
+
const params = { url };
|
|
340
|
+
transport.request(extApps.OPEN_LINK_METHOD, params).catch(() => {
|
|
341
|
+
});
|
|
342
|
+
},
|
|
343
|
+
updateModelContext(state, summary) {
|
|
344
|
+
if (destroyed) return;
|
|
345
|
+
const params = {
|
|
346
|
+
structuredContent: state,
|
|
347
|
+
...summary !== void 0 && {
|
|
348
|
+
content: [{ type: "text", text: summary }]
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
transport.send("ui/update-model-context", params);
|
|
352
|
+
},
|
|
353
|
+
async callTool(toolName, args) {
|
|
354
|
+
const params = {
|
|
355
|
+
name: toolName,
|
|
356
|
+
arguments: args ?? {}
|
|
357
|
+
};
|
|
358
|
+
const raw = await transport.request(
|
|
359
|
+
"tools/call",
|
|
360
|
+
params
|
|
361
|
+
);
|
|
362
|
+
return parseToolResult(raw);
|
|
363
|
+
},
|
|
364
|
+
sendMessage(text, context) {
|
|
365
|
+
if (destroyed) return;
|
|
366
|
+
const textBlock = {
|
|
367
|
+
type: "text",
|
|
368
|
+
text,
|
|
369
|
+
...context && { _meta: { context } }
|
|
370
|
+
};
|
|
371
|
+
const params = {
|
|
372
|
+
role: "user",
|
|
373
|
+
content: [textBlock]
|
|
374
|
+
};
|
|
375
|
+
transport.send(extApps.MESSAGE_METHOD, params);
|
|
376
|
+
},
|
|
377
|
+
destroy() {
|
|
378
|
+
if (destroyed) return;
|
|
379
|
+
destroyed = true;
|
|
380
|
+
resizer.destroy();
|
|
381
|
+
handlers.clear();
|
|
382
|
+
transport.destroy();
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
return app;
|
|
386
|
+
}
|
|
387
|
+
function injectCssVariables(vars) {
|
|
388
|
+
if (!vars || typeof vars !== "object") return;
|
|
389
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
390
|
+
if (typeof k === "string" && typeof v === "string") {
|
|
391
|
+
document.documentElement.style.setProperty(k, v);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// src/detection.ts
|
|
397
|
+
var DEFAULT_THEME = {
|
|
398
|
+
mode: "light",
|
|
399
|
+
primaryColor: "#6366f1",
|
|
400
|
+
tokens: {}
|
|
401
|
+
};
|
|
402
|
+
function detectHost(initResponse) {
|
|
403
|
+
const resp = initResponse;
|
|
404
|
+
const hostName = resp?.hostInfo?.name ?? "unknown";
|
|
405
|
+
const protocolVersion = resp?.protocolVersion ?? "unknown";
|
|
406
|
+
const ctx = resp?.hostContext;
|
|
407
|
+
const theme = extractTheme(ctx);
|
|
408
|
+
return {
|
|
409
|
+
isNimbleBrain: hostName === "nimblebrain",
|
|
410
|
+
serverName: hostName,
|
|
411
|
+
protocolVersion,
|
|
412
|
+
theme
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
function extractTheme(ctx) {
|
|
416
|
+
if (!ctx) return { ...DEFAULT_THEME };
|
|
417
|
+
const mode = ctx.theme === "light" || ctx.theme === "dark" ? ctx.theme : DEFAULT_THEME.mode;
|
|
418
|
+
const variables = ctx.styles?.variables;
|
|
419
|
+
const tokens = variables && typeof variables === "object" && !Array.isArray(variables) ? variables : {};
|
|
420
|
+
return { mode, primaryColor: DEFAULT_THEME.primaryColor, tokens };
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// src/keyboard.ts
|
|
424
|
+
var KeyboardForwarder = class {
|
|
425
|
+
listener;
|
|
426
|
+
destroyed = false;
|
|
427
|
+
constructor(transport, customKeys) {
|
|
428
|
+
const config = customKeys ?? null;
|
|
429
|
+
this.listener = (event) => {
|
|
430
|
+
if (this.destroyed) return;
|
|
431
|
+
if (this.shouldForward(event, config)) {
|
|
432
|
+
event.preventDefault();
|
|
433
|
+
transport.send("synapse/keydown", {
|
|
434
|
+
key: event.key,
|
|
435
|
+
ctrlKey: event.ctrlKey,
|
|
436
|
+
metaKey: event.metaKey,
|
|
437
|
+
shiftKey: event.shiftKey,
|
|
438
|
+
altKey: event.altKey
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
document.addEventListener("keydown", this.listener);
|
|
443
|
+
}
|
|
444
|
+
destroy() {
|
|
445
|
+
if (this.destroyed) return;
|
|
446
|
+
this.destroyed = true;
|
|
447
|
+
document.removeEventListener("keydown", this.listener);
|
|
448
|
+
}
|
|
449
|
+
shouldForward(event, config) {
|
|
450
|
+
if (config && config.length === 0) return false;
|
|
451
|
+
if (config) {
|
|
452
|
+
return config.some(
|
|
453
|
+
(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)
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
if (event.key === "Escape") return true;
|
|
457
|
+
if (event.ctrlKey || event.metaKey) {
|
|
458
|
+
const key = event.key.toLowerCase();
|
|
459
|
+
if (key === "c" || key === "v" || key === "x" || key === "a") return false;
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
// src/core.ts
|
|
467
|
+
function createSynapse(options) {
|
|
468
|
+
const { name, version, internal = false, forwardKeys } = options;
|
|
469
|
+
const transport = new SynapseTransport();
|
|
470
|
+
let hostInfo = null;
|
|
471
|
+
let currentTheme = {
|
|
472
|
+
mode: "light",
|
|
473
|
+
primaryColor: "#6366f1",
|
|
474
|
+
tokens: {}
|
|
475
|
+
};
|
|
476
|
+
let destroyed = false;
|
|
477
|
+
let stateTimer = null;
|
|
478
|
+
let keyboard = null;
|
|
479
|
+
const initParams = {
|
|
480
|
+
protocolVersion: extApps.LATEST_PROTOCOL_VERSION,
|
|
481
|
+
appInfo: { name, version },
|
|
482
|
+
appCapabilities: {}
|
|
483
|
+
};
|
|
484
|
+
const ready = transport.request(extApps.INITIALIZE_METHOD, initParams).then((result) => {
|
|
485
|
+
hostInfo = detectHost(result);
|
|
486
|
+
currentTheme = hostInfo.theme;
|
|
487
|
+
transport.send(extApps.INITIALIZED_METHOD, {});
|
|
488
|
+
keyboard = new KeyboardForwarder(transport, forwardKeys);
|
|
489
|
+
});
|
|
490
|
+
const unsubTheme = transport.onMessage(extApps.HOST_CONTEXT_CHANGED_METHOD, (params) => {
|
|
491
|
+
if (!params) return;
|
|
492
|
+
const mode = params.theme === "dark" ? "dark" : "light";
|
|
493
|
+
const styles = params.styles;
|
|
494
|
+
const variables = styles?.variables;
|
|
495
|
+
const tokens = variables && typeof variables === "object" ? variables : currentTheme.tokens;
|
|
496
|
+
currentTheme = { mode, primaryColor: currentTheme.primaryColor, tokens };
|
|
497
|
+
for (const cb of themeCallbacks) cb(currentTheme);
|
|
498
|
+
});
|
|
499
|
+
const themeCallbacks = /* @__PURE__ */ new Set();
|
|
500
|
+
const dataCallbacks = /* @__PURE__ */ new Set();
|
|
501
|
+
const actionCallbacks = /* @__PURE__ */ new Set();
|
|
502
|
+
const unsubData = transport.onMessage("synapse/data-changed", (params) => {
|
|
503
|
+
if (!params) return;
|
|
504
|
+
const event = {
|
|
505
|
+
source: "agent",
|
|
506
|
+
server: params.server ?? "",
|
|
507
|
+
tool: params.tool ?? ""
|
|
508
|
+
};
|
|
509
|
+
for (const cb of dataCallbacks) cb(event);
|
|
510
|
+
});
|
|
511
|
+
const unsubAction = transport.onMessage("synapse/action", (params) => {
|
|
512
|
+
if (!params || typeof params.type !== "string") return;
|
|
513
|
+
const action = {
|
|
514
|
+
type: params.type,
|
|
515
|
+
payload: params.payload ?? {},
|
|
516
|
+
requiresConfirmation: params.requiresConfirmation === true,
|
|
517
|
+
label: typeof params.label === "string" ? params.label : void 0
|
|
518
|
+
};
|
|
519
|
+
for (const cb of actionCallbacks) cb(action);
|
|
520
|
+
});
|
|
521
|
+
const isNB = () => hostInfo?.isNimbleBrain === true;
|
|
522
|
+
const synapse = {
|
|
523
|
+
get ready() {
|
|
524
|
+
return ready;
|
|
525
|
+
},
|
|
526
|
+
get isNimbleBrainHost() {
|
|
527
|
+
return isNB();
|
|
528
|
+
},
|
|
529
|
+
get destroyed() {
|
|
530
|
+
return destroyed;
|
|
531
|
+
},
|
|
532
|
+
async callTool(toolName, args) {
|
|
533
|
+
const params = {
|
|
534
|
+
name: toolName,
|
|
535
|
+
arguments: args ?? {}
|
|
536
|
+
};
|
|
537
|
+
if (internal) {
|
|
538
|
+
params.server = name;
|
|
539
|
+
}
|
|
540
|
+
const raw = await transport.request("tools/call", params);
|
|
541
|
+
return parseToolResult(raw);
|
|
542
|
+
},
|
|
543
|
+
onDataChanged(callback) {
|
|
544
|
+
dataCallbacks.add(callback);
|
|
545
|
+
return () => {
|
|
546
|
+
dataCallbacks.delete(callback);
|
|
547
|
+
};
|
|
548
|
+
},
|
|
549
|
+
onAction(callback) {
|
|
550
|
+
actionCallbacks.add(callback);
|
|
551
|
+
return () => {
|
|
552
|
+
actionCallbacks.delete(callback);
|
|
553
|
+
};
|
|
554
|
+
},
|
|
555
|
+
getTheme() {
|
|
556
|
+
return { ...currentTheme };
|
|
557
|
+
},
|
|
558
|
+
onThemeChanged(callback) {
|
|
559
|
+
themeCallbacks.add(callback);
|
|
560
|
+
return () => {
|
|
561
|
+
themeCallbacks.delete(callback);
|
|
562
|
+
};
|
|
563
|
+
},
|
|
564
|
+
action(action, params) {
|
|
565
|
+
if (!isNB()) return;
|
|
566
|
+
transport.send("synapse/action", { action, ...params });
|
|
567
|
+
},
|
|
568
|
+
chat(message, context) {
|
|
569
|
+
const textBlock = {
|
|
570
|
+
type: "text",
|
|
571
|
+
text: message,
|
|
572
|
+
...isNB() && context && { _meta: { context } }
|
|
573
|
+
};
|
|
574
|
+
const params = {
|
|
575
|
+
role: "user",
|
|
576
|
+
content: [textBlock]
|
|
577
|
+
};
|
|
578
|
+
transport.send(extApps.MESSAGE_METHOD, params);
|
|
579
|
+
},
|
|
580
|
+
setVisibleState(state, summary) {
|
|
581
|
+
if (stateTimer) clearTimeout(stateTimer);
|
|
582
|
+
stateTimer = setTimeout(() => {
|
|
583
|
+
const params = {
|
|
584
|
+
structuredContent: state,
|
|
585
|
+
...summary !== void 0 && {
|
|
586
|
+
content: [{ type: "text", text: summary }]
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
transport.send("ui/update-model-context", params);
|
|
590
|
+
stateTimer = null;
|
|
591
|
+
}, 250);
|
|
592
|
+
},
|
|
593
|
+
saveFile(filename, content, mimeType) {
|
|
594
|
+
const data = typeof content === "string" ? content : "[Blob content not serializable]";
|
|
595
|
+
transport.send("synapse/save-file", {
|
|
596
|
+
data,
|
|
597
|
+
filename,
|
|
598
|
+
mimeType: mimeType ?? "application/octet-stream"
|
|
599
|
+
});
|
|
600
|
+
},
|
|
601
|
+
openLink(url) {
|
|
602
|
+
const params = { url };
|
|
603
|
+
transport.request(extApps.OPEN_LINK_METHOD, params).catch(() => {
|
|
604
|
+
window.open(url, "_blank", "noopener");
|
|
605
|
+
});
|
|
606
|
+
},
|
|
607
|
+
async pickFile(options2) {
|
|
608
|
+
if (!isNB()) {
|
|
609
|
+
throw new Error("pickFile is not supported in this host");
|
|
610
|
+
}
|
|
611
|
+
const result = await transport.request("synapse/pick-file", {
|
|
612
|
+
accept: options2?.accept,
|
|
613
|
+
maxSize: options2?.maxSize ?? 26214400,
|
|
614
|
+
multiple: false
|
|
615
|
+
});
|
|
616
|
+
return result ?? null;
|
|
617
|
+
},
|
|
618
|
+
async pickFiles(options2) {
|
|
619
|
+
if (!isNB()) {
|
|
620
|
+
throw new Error("pickFiles is not supported in this host");
|
|
621
|
+
}
|
|
622
|
+
const result = await transport.request("synapse/pick-file", {
|
|
623
|
+
accept: options2?.accept,
|
|
624
|
+
maxSize: options2?.maxSize ?? 26214400,
|
|
625
|
+
multiple: true
|
|
626
|
+
});
|
|
627
|
+
if (!result) return [];
|
|
628
|
+
return Array.isArray(result) ? result : [result];
|
|
629
|
+
},
|
|
630
|
+
_onMessage(method, callback) {
|
|
631
|
+
return transport.onMessage(method, callback);
|
|
632
|
+
},
|
|
633
|
+
_request(method, params) {
|
|
634
|
+
return transport.request(method, params);
|
|
635
|
+
},
|
|
636
|
+
destroy() {
|
|
637
|
+
if (destroyed) return;
|
|
638
|
+
destroyed = true;
|
|
639
|
+
if (stateTimer) clearTimeout(stateTimer);
|
|
640
|
+
keyboard?.destroy();
|
|
641
|
+
unsubTheme();
|
|
642
|
+
unsubData();
|
|
643
|
+
unsubAction();
|
|
644
|
+
themeCallbacks.clear();
|
|
645
|
+
dataCallbacks.clear();
|
|
646
|
+
actionCallbacks.clear();
|
|
647
|
+
transport.destroy();
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
return synapse;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
exports.connect = connect;
|
|
654
|
+
exports.createSynapse = createSynapse;
|
|
655
|
+
//# sourceMappingURL=chunk-54YIV4ZL.cjs.map
|
|
656
|
+
//# sourceMappingURL=chunk-54YIV4ZL.cjs.map
|