@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
|
@@ -121,20 +121,20 @@ interface Synapse {
|
|
|
121
121
|
* Debounced at 250ms. Each call overwrites the previous context.
|
|
122
122
|
*/
|
|
123
123
|
setVisibleState(state: Record<string, unknown>, summary?: string): void;
|
|
124
|
-
|
|
124
|
+
saveFile(filename: string, content: string | Blob, mimeType?: string): void;
|
|
125
125
|
openLink(url: string): void;
|
|
126
126
|
/**
|
|
127
127
|
* Request a file from the user via the host's native file picker.
|
|
128
128
|
* NimbleBrain-only: throws in non-NimbleBrain hosts.
|
|
129
129
|
* Returns null if the user cancels.
|
|
130
130
|
*/
|
|
131
|
-
|
|
131
|
+
pickFile(options?: RequestFileOptions): Promise<FileResult | null>;
|
|
132
132
|
/**
|
|
133
|
-
*
|
|
133
|
+
* Pick multiple files from the user.
|
|
134
134
|
* NimbleBrain-only: throws in non-NimbleBrain hosts.
|
|
135
135
|
* Returns empty array if the user cancels.
|
|
136
136
|
*/
|
|
137
|
-
|
|
137
|
+
pickFiles(options?: RequestFileOptions): Promise<FileResult[]>;
|
|
138
138
|
/** @internal — used by createStore for synapse/state-loaded */
|
|
139
139
|
_onMessage(method: string, callback: (params: Record<string, unknown> | undefined) => void): () => void;
|
|
140
140
|
/** @internal — used by createStore for synapse/persist-state */
|
|
@@ -189,5 +189,55 @@ interface HostInfo {
|
|
|
189
189
|
protocolVersion: string;
|
|
190
190
|
theme: SynapseTheme;
|
|
191
191
|
}
|
|
192
|
+
interface ConnectOptions {
|
|
193
|
+
name: string;
|
|
194
|
+
version: string;
|
|
195
|
+
autoResize?: boolean;
|
|
196
|
+
/** Pre-register event handlers before the handshake completes.
|
|
197
|
+
* These are wired before `initialized` is sent, so no messages are lost. */
|
|
198
|
+
on?: Record<string, (data: any) => void>;
|
|
199
|
+
}
|
|
200
|
+
interface Theme {
|
|
201
|
+
mode: "light" | "dark";
|
|
202
|
+
tokens: Record<string, string>;
|
|
203
|
+
}
|
|
204
|
+
interface Dimensions {
|
|
205
|
+
width?: number;
|
|
206
|
+
height?: number;
|
|
207
|
+
maxWidth?: number;
|
|
208
|
+
maxHeight?: number;
|
|
209
|
+
}
|
|
210
|
+
interface ToolResultData {
|
|
211
|
+
content: unknown;
|
|
212
|
+
structuredContent: unknown;
|
|
213
|
+
raw: Record<string, unknown>;
|
|
214
|
+
}
|
|
215
|
+
/** Known short event names for App.on() */
|
|
216
|
+
type AppEventName = "tool-result" | "tool-input" | "tool-input-partial" | "tool-cancelled" | "theme-changed" | "teardown";
|
|
217
|
+
interface App {
|
|
218
|
+
readonly theme: Theme;
|
|
219
|
+
readonly hostInfo: {
|
|
220
|
+
name: string;
|
|
221
|
+
version: string;
|
|
222
|
+
};
|
|
223
|
+
readonly toolInfo: {
|
|
224
|
+
tool: Record<string, unknown>;
|
|
225
|
+
} | null;
|
|
226
|
+
readonly containerDimensions: Dimensions | null;
|
|
227
|
+
on(event: "tool-input", handler: (args: Record<string, unknown>) => void): () => void;
|
|
228
|
+
on(event: "tool-result", handler: (data: ToolResultData) => void): () => void;
|
|
229
|
+
on(event: "theme-changed", handler: (theme: Theme) => void): () => void;
|
|
230
|
+
on(event: "teardown", handler: () => void): () => void;
|
|
231
|
+
on(event: string, handler: (params: unknown) => void): () => void;
|
|
232
|
+
resize(width?: number, height?: number): void;
|
|
233
|
+
openLink(url: string): void;
|
|
234
|
+
updateModelContext(state: Record<string, unknown>, summary?: string): void;
|
|
235
|
+
callTool(name: string, args?: Record<string, unknown>): Promise<ToolCallResult>;
|
|
236
|
+
sendMessage(text: string, context?: {
|
|
237
|
+
action?: string;
|
|
238
|
+
entity?: string;
|
|
239
|
+
}): void;
|
|
240
|
+
destroy(): void;
|
|
241
|
+
}
|
|
192
242
|
|
|
193
|
-
export type {
|
|
243
|
+
export type { App as A, BuiltinActionType as B, ConnectOptions as C, DataChangedEvent as D, FileResult as F, HostInfo as H, KeyForwardConfig as K, NavigatePayload as N, RequestFileOptions as R, SynapseOptions as S, ToolDefinition as T, VisibleState as V, Synapse as a, ActionReducer as b, StoreConfig as c, Store as d, AgentAction as e, AppEventName as f, Dimensions as g, NotifyPayload as h, StateAcknowledgement as i, StoreDispatch as j, SynapseTheme as k, Theme as l, ToolCallResult as m, ToolResultData as n };
|
|
@@ -121,20 +121,20 @@ interface Synapse {
|
|
|
121
121
|
* Debounced at 250ms. Each call overwrites the previous context.
|
|
122
122
|
*/
|
|
123
123
|
setVisibleState(state: Record<string, unknown>, summary?: string): void;
|
|
124
|
-
|
|
124
|
+
saveFile(filename: string, content: string | Blob, mimeType?: string): void;
|
|
125
125
|
openLink(url: string): void;
|
|
126
126
|
/**
|
|
127
127
|
* Request a file from the user via the host's native file picker.
|
|
128
128
|
* NimbleBrain-only: throws in non-NimbleBrain hosts.
|
|
129
129
|
* Returns null if the user cancels.
|
|
130
130
|
*/
|
|
131
|
-
|
|
131
|
+
pickFile(options?: RequestFileOptions): Promise<FileResult | null>;
|
|
132
132
|
/**
|
|
133
|
-
*
|
|
133
|
+
* Pick multiple files from the user.
|
|
134
134
|
* NimbleBrain-only: throws in non-NimbleBrain hosts.
|
|
135
135
|
* Returns empty array if the user cancels.
|
|
136
136
|
*/
|
|
137
|
-
|
|
137
|
+
pickFiles(options?: RequestFileOptions): Promise<FileResult[]>;
|
|
138
138
|
/** @internal — used by createStore for synapse/state-loaded */
|
|
139
139
|
_onMessage(method: string, callback: (params: Record<string, unknown> | undefined) => void): () => void;
|
|
140
140
|
/** @internal — used by createStore for synapse/persist-state */
|
|
@@ -189,5 +189,55 @@ interface HostInfo {
|
|
|
189
189
|
protocolVersion: string;
|
|
190
190
|
theme: SynapseTheme;
|
|
191
191
|
}
|
|
192
|
+
interface ConnectOptions {
|
|
193
|
+
name: string;
|
|
194
|
+
version: string;
|
|
195
|
+
autoResize?: boolean;
|
|
196
|
+
/** Pre-register event handlers before the handshake completes.
|
|
197
|
+
* These are wired before `initialized` is sent, so no messages are lost. */
|
|
198
|
+
on?: Record<string, (data: any) => void>;
|
|
199
|
+
}
|
|
200
|
+
interface Theme {
|
|
201
|
+
mode: "light" | "dark";
|
|
202
|
+
tokens: Record<string, string>;
|
|
203
|
+
}
|
|
204
|
+
interface Dimensions {
|
|
205
|
+
width?: number;
|
|
206
|
+
height?: number;
|
|
207
|
+
maxWidth?: number;
|
|
208
|
+
maxHeight?: number;
|
|
209
|
+
}
|
|
210
|
+
interface ToolResultData {
|
|
211
|
+
content: unknown;
|
|
212
|
+
structuredContent: unknown;
|
|
213
|
+
raw: Record<string, unknown>;
|
|
214
|
+
}
|
|
215
|
+
/** Known short event names for App.on() */
|
|
216
|
+
type AppEventName = "tool-result" | "tool-input" | "tool-input-partial" | "tool-cancelled" | "theme-changed" | "teardown";
|
|
217
|
+
interface App {
|
|
218
|
+
readonly theme: Theme;
|
|
219
|
+
readonly hostInfo: {
|
|
220
|
+
name: string;
|
|
221
|
+
version: string;
|
|
222
|
+
};
|
|
223
|
+
readonly toolInfo: {
|
|
224
|
+
tool: Record<string, unknown>;
|
|
225
|
+
} | null;
|
|
226
|
+
readonly containerDimensions: Dimensions | null;
|
|
227
|
+
on(event: "tool-input", handler: (args: Record<string, unknown>) => void): () => void;
|
|
228
|
+
on(event: "tool-result", handler: (data: ToolResultData) => void): () => void;
|
|
229
|
+
on(event: "theme-changed", handler: (theme: Theme) => void): () => void;
|
|
230
|
+
on(event: "teardown", handler: () => void): () => void;
|
|
231
|
+
on(event: string, handler: (params: unknown) => void): () => void;
|
|
232
|
+
resize(width?: number, height?: number): void;
|
|
233
|
+
openLink(url: string): void;
|
|
234
|
+
updateModelContext(state: Record<string, unknown>, summary?: string): void;
|
|
235
|
+
callTool(name: string, args?: Record<string, unknown>): Promise<ToolCallResult>;
|
|
236
|
+
sendMessage(text: string, context?: {
|
|
237
|
+
action?: string;
|
|
238
|
+
entity?: string;
|
|
239
|
+
}): void;
|
|
240
|
+
destroy(): void;
|
|
241
|
+
}
|
|
192
242
|
|
|
193
|
-
export type {
|
|
243
|
+
export type { App as A, BuiltinActionType as B, ConnectOptions as C, DataChangedEvent as D, FileResult as F, HostInfo as H, KeyForwardConfig as K, NavigatePayload as N, RequestFileOptions as R, SynapseOptions as S, ToolDefinition as T, VisibleState as V, Synapse as a, ActionReducer as b, StoreConfig as c, Store as d, AgentAction as e, AppEventName as f, Dimensions as g, NotifyPayload as h, StateAcknowledgement as i, StoreDispatch as j, SynapseTheme as k, Theme as l, ToolCallResult as m, ToolResultData as n };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nimblebrain/synapse",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Agent-aware app SDK for the MCP ext-apps protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
"types": "./dist/codegen/index.d.ts",
|
|
24
24
|
"import": "./dist/codegen/index.js",
|
|
25
25
|
"require": "./dist/codegen/index.cjs"
|
|
26
|
+
},
|
|
27
|
+
"./iife": {
|
|
28
|
+
"default": "./dist/connect.iife.global.js"
|
|
26
29
|
}
|
|
27
30
|
},
|
|
28
31
|
"bin": {
|
package/dist/chunk-MQNKIR7K.cjs
DELETED
|
@@ -1,406 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// src/detection.ts
|
|
4
|
-
var DEFAULT_THEME = {
|
|
5
|
-
mode: "light",
|
|
6
|
-
primaryColor: "#6366f1",
|
|
7
|
-
tokens: {}
|
|
8
|
-
};
|
|
9
|
-
function detectHost(initResponse) {
|
|
10
|
-
const resp = initResponse;
|
|
11
|
-
const serverInfo = safeObj(resp?.serverInfo);
|
|
12
|
-
const serverName = typeof serverInfo?.name === "string" ? serverInfo.name : "unknown";
|
|
13
|
-
const protocolVersion = typeof resp?.protocolVersion === "string" ? resp.protocolVersion : "unknown";
|
|
14
|
-
const hostContext = safeObj(resp?.hostContext);
|
|
15
|
-
const theme = extractTheme(hostContext?.theme);
|
|
16
|
-
return {
|
|
17
|
-
isNimbleBrain: serverName === "nimblebrain",
|
|
18
|
-
serverName,
|
|
19
|
-
protocolVersion,
|
|
20
|
-
theme
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
function extractTheme(raw) {
|
|
24
|
-
const obj = safeObj(raw);
|
|
25
|
-
if (!obj) return { ...DEFAULT_THEME };
|
|
26
|
-
const mode = obj.mode === "light" || obj.mode === "dark" ? obj.mode : DEFAULT_THEME.mode;
|
|
27
|
-
const primaryColor = typeof obj.primaryColor === "string" ? obj.primaryColor : DEFAULT_THEME.primaryColor;
|
|
28
|
-
const tokens = obj.tokens !== null && typeof obj.tokens === "object" && !Array.isArray(obj.tokens) ? obj.tokens : {};
|
|
29
|
-
return { mode, primaryColor, tokens };
|
|
30
|
-
}
|
|
31
|
-
function safeObj(value) {
|
|
32
|
-
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
33
|
-
return value;
|
|
34
|
-
}
|
|
35
|
-
return void 0;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// src/keyboard.ts
|
|
39
|
-
var KeyboardForwarder = class {
|
|
40
|
-
listener;
|
|
41
|
-
destroyed = false;
|
|
42
|
-
constructor(transport, customKeys) {
|
|
43
|
-
const config = customKeys ?? null;
|
|
44
|
-
this.listener = (event) => {
|
|
45
|
-
if (this.destroyed) return;
|
|
46
|
-
if (this.shouldForward(event, config)) {
|
|
47
|
-
event.preventDefault();
|
|
48
|
-
transport.send("synapse/keydown", {
|
|
49
|
-
key: event.key,
|
|
50
|
-
ctrlKey: event.ctrlKey,
|
|
51
|
-
metaKey: event.metaKey,
|
|
52
|
-
shiftKey: event.shiftKey,
|
|
53
|
-
altKey: event.altKey
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
document.addEventListener("keydown", this.listener);
|
|
58
|
-
}
|
|
59
|
-
destroy() {
|
|
60
|
-
if (this.destroyed) return;
|
|
61
|
-
this.destroyed = true;
|
|
62
|
-
document.removeEventListener("keydown", this.listener);
|
|
63
|
-
}
|
|
64
|
-
shouldForward(event, config) {
|
|
65
|
-
if (config && config.length === 0) return false;
|
|
66
|
-
if (config) {
|
|
67
|
-
return config.some(
|
|
68
|
-
(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)
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
if (event.key === "Escape") return true;
|
|
72
|
-
if (event.ctrlKey || event.metaKey) {
|
|
73
|
-
const key = event.key.toLowerCase();
|
|
74
|
-
if (key === "c" || key === "v" || key === "x" || key === "a") return false;
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
// src/result-parser.ts
|
|
82
|
-
function parseToolResult(raw) {
|
|
83
|
-
if (raw == null) {
|
|
84
|
-
return { data: null, isError: false };
|
|
85
|
-
}
|
|
86
|
-
if (isCallToolResult(raw)) {
|
|
87
|
-
return parseCallToolResult(raw);
|
|
88
|
-
}
|
|
89
|
-
return { data: raw, isError: false };
|
|
90
|
-
}
|
|
91
|
-
function isCallToolResult(value) {
|
|
92
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
return Array.isArray(value.content);
|
|
96
|
-
}
|
|
97
|
-
function isTextBlock(block) {
|
|
98
|
-
if (block === null || typeof block !== "object" || Array.isArray(block)) {
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
const obj = block;
|
|
102
|
-
return obj.type === "text" && typeof obj.text === "string";
|
|
103
|
-
}
|
|
104
|
-
function parseCallToolResult(result) {
|
|
105
|
-
const isError = result.isError === true;
|
|
106
|
-
const content = result.content;
|
|
107
|
-
if (content.length === 0) {
|
|
108
|
-
return { data: null, isError };
|
|
109
|
-
}
|
|
110
|
-
const firstText = content.find(isTextBlock);
|
|
111
|
-
if (!firstText) {
|
|
112
|
-
return { data: content, isError };
|
|
113
|
-
}
|
|
114
|
-
try {
|
|
115
|
-
return { data: JSON.parse(firstText.text), isError };
|
|
116
|
-
} catch {
|
|
117
|
-
return { data: firstText.text, isError };
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// src/transport.ts
|
|
122
|
-
var SynapseTransport = class {
|
|
123
|
-
counter = 0;
|
|
124
|
-
destroyed = false;
|
|
125
|
-
pending = /* @__PURE__ */ new Map();
|
|
126
|
-
handlers = /* @__PURE__ */ new Map();
|
|
127
|
-
listener;
|
|
128
|
-
constructor() {
|
|
129
|
-
this.listener = (event) => this.handleMessage(event);
|
|
130
|
-
window.addEventListener("message", this.listener);
|
|
131
|
-
}
|
|
132
|
-
send(method, params) {
|
|
133
|
-
if (this.destroyed) return;
|
|
134
|
-
const msg = {
|
|
135
|
-
jsonrpc: "2.0",
|
|
136
|
-
method,
|
|
137
|
-
...params !== void 0 && { params }
|
|
138
|
-
};
|
|
139
|
-
window.parent.postMessage(msg, "*");
|
|
140
|
-
}
|
|
141
|
-
request(method, params) {
|
|
142
|
-
if (this.destroyed) {
|
|
143
|
-
return Promise.reject(new Error("Transport destroyed"));
|
|
144
|
-
}
|
|
145
|
-
const id = `syn-${++this.counter}`;
|
|
146
|
-
const msg = {
|
|
147
|
-
jsonrpc: "2.0",
|
|
148
|
-
method,
|
|
149
|
-
id,
|
|
150
|
-
...params !== void 0 && { params }
|
|
151
|
-
};
|
|
152
|
-
return new Promise((resolve, reject) => {
|
|
153
|
-
this.pending.set(id, { resolve, reject });
|
|
154
|
-
window.parent.postMessage(msg, "*");
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
onMessage(method, callback) {
|
|
158
|
-
if (!this.handlers.has(method)) {
|
|
159
|
-
this.handlers.set(method, /* @__PURE__ */ new Set());
|
|
160
|
-
}
|
|
161
|
-
this.handlers.get(method)?.add(callback);
|
|
162
|
-
return () => {
|
|
163
|
-
const set = this.handlers.get(method);
|
|
164
|
-
if (set) {
|
|
165
|
-
set.delete(callback);
|
|
166
|
-
if (set.size === 0) {
|
|
167
|
-
this.handlers.delete(method);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
destroy() {
|
|
173
|
-
if (this.destroyed) return;
|
|
174
|
-
this.destroyed = true;
|
|
175
|
-
window.removeEventListener("message", this.listener);
|
|
176
|
-
const error = new Error("Transport destroyed");
|
|
177
|
-
for (const entry of this.pending.values()) {
|
|
178
|
-
entry.reject(error);
|
|
179
|
-
}
|
|
180
|
-
this.pending.clear();
|
|
181
|
-
this.handlers.clear();
|
|
182
|
-
}
|
|
183
|
-
handleMessage(event) {
|
|
184
|
-
if (this.destroyed) return;
|
|
185
|
-
const data = event.data;
|
|
186
|
-
if (!data || data.jsonrpc !== "2.0") return;
|
|
187
|
-
if ("id" in data && data.id && !("method" in data)) {
|
|
188
|
-
const response = data;
|
|
189
|
-
const entry = this.pending.get(response.id);
|
|
190
|
-
if (!entry) return;
|
|
191
|
-
this.pending.delete(response.id);
|
|
192
|
-
if (response.error) {
|
|
193
|
-
const err = new Error(response.error.message);
|
|
194
|
-
err.code = response.error.code;
|
|
195
|
-
err.data = response.error.data;
|
|
196
|
-
entry.reject(err);
|
|
197
|
-
} else {
|
|
198
|
-
entry.resolve(response.result);
|
|
199
|
-
}
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
if ("method" in data && !("id" in data && data.id)) {
|
|
203
|
-
const notification = data;
|
|
204
|
-
const set = this.handlers.get(notification.method);
|
|
205
|
-
if (set) {
|
|
206
|
-
for (const handler of set) {
|
|
207
|
-
handler(notification.params);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
// src/core.ts
|
|
215
|
-
function createSynapse(options) {
|
|
216
|
-
const { name, version, internal = false, forwardKeys } = options;
|
|
217
|
-
const transport = new SynapseTransport();
|
|
218
|
-
let hostInfo = null;
|
|
219
|
-
let currentTheme = {
|
|
220
|
-
mode: "light",
|
|
221
|
-
primaryColor: "#6366f1",
|
|
222
|
-
tokens: {}
|
|
223
|
-
};
|
|
224
|
-
let destroyed = false;
|
|
225
|
-
let stateTimer = null;
|
|
226
|
-
let keyboard = null;
|
|
227
|
-
const ready = transport.request("ui/initialize", {
|
|
228
|
-
protocolVersion: "2026-01-26",
|
|
229
|
-
clientInfo: { name, version },
|
|
230
|
-
capabilities: {}
|
|
231
|
-
}).then((result) => {
|
|
232
|
-
hostInfo = detectHost(result);
|
|
233
|
-
currentTheme = hostInfo.theme;
|
|
234
|
-
transport.send("ui/notifications/initialized", {});
|
|
235
|
-
keyboard = new KeyboardForwarder(transport, forwardKeys);
|
|
236
|
-
});
|
|
237
|
-
const unsubTheme = transport.onMessage("ui/notifications/host-context-changed", (params) => {
|
|
238
|
-
if (!params) return;
|
|
239
|
-
const mode = params.theme === "dark" ? "dark" : "light";
|
|
240
|
-
const tokens = params.tokens && typeof params.tokens === "object" ? params.tokens : currentTheme.tokens;
|
|
241
|
-
currentTheme = { mode, primaryColor: currentTheme.primaryColor, tokens };
|
|
242
|
-
for (const cb of themeCallbacks) cb(currentTheme);
|
|
243
|
-
});
|
|
244
|
-
const unsubNbTheme = transport.onMessage("synapse/theme-changed", (params) => {
|
|
245
|
-
if (!params) return;
|
|
246
|
-
const mode = params.mode === "dark" || params.mode === "light" ? params.mode : currentTheme.mode;
|
|
247
|
-
const tokens = params.tokens && typeof params.tokens === "object" ? params.tokens : currentTheme.tokens;
|
|
248
|
-
currentTheme = { mode, primaryColor: currentTheme.primaryColor, tokens };
|
|
249
|
-
for (const cb of themeCallbacks) cb(currentTheme);
|
|
250
|
-
});
|
|
251
|
-
const themeCallbacks = /* @__PURE__ */ new Set();
|
|
252
|
-
const dataCallbacks = /* @__PURE__ */ new Set();
|
|
253
|
-
const actionCallbacks = /* @__PURE__ */ new Set();
|
|
254
|
-
const unsubData = transport.onMessage("synapse/data-changed", (params) => {
|
|
255
|
-
if (!params) return;
|
|
256
|
-
const event = {
|
|
257
|
-
source: "agent",
|
|
258
|
-
server: params.server ?? "",
|
|
259
|
-
tool: params.tool ?? ""
|
|
260
|
-
};
|
|
261
|
-
for (const cb of dataCallbacks) cb(event);
|
|
262
|
-
});
|
|
263
|
-
const unsubAction = transport.onMessage("synapse/action", (params) => {
|
|
264
|
-
if (!params || typeof params.type !== "string") return;
|
|
265
|
-
const action = {
|
|
266
|
-
type: params.type,
|
|
267
|
-
payload: params.payload ?? {},
|
|
268
|
-
requiresConfirmation: params.requiresConfirmation === true,
|
|
269
|
-
label: typeof params.label === "string" ? params.label : void 0
|
|
270
|
-
};
|
|
271
|
-
for (const cb of actionCallbacks) cb(action);
|
|
272
|
-
});
|
|
273
|
-
const isNB = () => hostInfo?.isNimbleBrain === true;
|
|
274
|
-
const synapse = {
|
|
275
|
-
get ready() {
|
|
276
|
-
return ready;
|
|
277
|
-
},
|
|
278
|
-
get isNimbleBrainHost() {
|
|
279
|
-
return isNB();
|
|
280
|
-
},
|
|
281
|
-
get destroyed() {
|
|
282
|
-
return destroyed;
|
|
283
|
-
},
|
|
284
|
-
async callTool(toolName, args) {
|
|
285
|
-
const params = {
|
|
286
|
-
name: toolName,
|
|
287
|
-
arguments: args ?? {}
|
|
288
|
-
};
|
|
289
|
-
if (internal) {
|
|
290
|
-
params.server = name;
|
|
291
|
-
}
|
|
292
|
-
const raw = await transport.request("tools/call", params);
|
|
293
|
-
return parseToolResult(raw);
|
|
294
|
-
},
|
|
295
|
-
onDataChanged(callback) {
|
|
296
|
-
dataCallbacks.add(callback);
|
|
297
|
-
return () => {
|
|
298
|
-
dataCallbacks.delete(callback);
|
|
299
|
-
};
|
|
300
|
-
},
|
|
301
|
-
onAction(callback) {
|
|
302
|
-
actionCallbacks.add(callback);
|
|
303
|
-
return () => {
|
|
304
|
-
actionCallbacks.delete(callback);
|
|
305
|
-
};
|
|
306
|
-
},
|
|
307
|
-
getTheme() {
|
|
308
|
-
return { ...currentTheme };
|
|
309
|
-
},
|
|
310
|
-
onThemeChanged(callback) {
|
|
311
|
-
themeCallbacks.add(callback);
|
|
312
|
-
return () => {
|
|
313
|
-
themeCallbacks.delete(callback);
|
|
314
|
-
};
|
|
315
|
-
},
|
|
316
|
-
action(action, params) {
|
|
317
|
-
if (!isNB()) return;
|
|
318
|
-
transport.send("synapse/action", { action, ...params });
|
|
319
|
-
},
|
|
320
|
-
chat(message, context) {
|
|
321
|
-
const textBlock = { type: "text", text: message };
|
|
322
|
-
if (isNB() && context) {
|
|
323
|
-
textBlock._meta = { context };
|
|
324
|
-
}
|
|
325
|
-
transport.send("ui/message", {
|
|
326
|
-
role: "user",
|
|
327
|
-
content: [textBlock]
|
|
328
|
-
});
|
|
329
|
-
},
|
|
330
|
-
setVisibleState(state, summary) {
|
|
331
|
-
if (stateTimer) clearTimeout(stateTimer);
|
|
332
|
-
stateTimer = setTimeout(() => {
|
|
333
|
-
transport.send("ui/update-model-context", {
|
|
334
|
-
structuredContent: state,
|
|
335
|
-
...summary !== void 0 && {
|
|
336
|
-
content: [{ type: "text", text: summary }]
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
stateTimer = null;
|
|
340
|
-
}, 250);
|
|
341
|
-
},
|
|
342
|
-
downloadFile(filename, content, mimeType) {
|
|
343
|
-
if (!isNB()) return;
|
|
344
|
-
const data = typeof content === "string" ? content : "[Blob content not serializable]";
|
|
345
|
-
transport.send("synapse/download-file", {
|
|
346
|
-
data,
|
|
347
|
-
filename,
|
|
348
|
-
mimeType: mimeType ?? "application/octet-stream"
|
|
349
|
-
});
|
|
350
|
-
},
|
|
351
|
-
openLink(url) {
|
|
352
|
-
transport.send("ui/open-link", { url });
|
|
353
|
-
if (!isNB()) {
|
|
354
|
-
window.open(url, "_blank", "noopener");
|
|
355
|
-
}
|
|
356
|
-
},
|
|
357
|
-
async requestFile(options2) {
|
|
358
|
-
if (!isNB()) {
|
|
359
|
-
throw new Error("requestFile is not supported in this host");
|
|
360
|
-
}
|
|
361
|
-
const result = await transport.request("synapse/request-file", {
|
|
362
|
-
accept: options2?.accept,
|
|
363
|
-
maxSize: options2?.maxSize ?? 26214400,
|
|
364
|
-
multiple: false
|
|
365
|
-
});
|
|
366
|
-
return result ?? null;
|
|
367
|
-
},
|
|
368
|
-
async requestFiles(options2) {
|
|
369
|
-
if (!isNB()) {
|
|
370
|
-
throw new Error("requestFiles is not supported in this host");
|
|
371
|
-
}
|
|
372
|
-
const result = await transport.request("synapse/request-file", {
|
|
373
|
-
accept: options2?.accept,
|
|
374
|
-
maxSize: options2?.maxSize ?? 26214400,
|
|
375
|
-
multiple: true
|
|
376
|
-
});
|
|
377
|
-
if (!result) return [];
|
|
378
|
-
return Array.isArray(result) ? result : [result];
|
|
379
|
-
},
|
|
380
|
-
_onMessage(method, callback) {
|
|
381
|
-
return transport.onMessage(method, callback);
|
|
382
|
-
},
|
|
383
|
-
_request(method, params) {
|
|
384
|
-
return transport.request(method, params);
|
|
385
|
-
},
|
|
386
|
-
destroy() {
|
|
387
|
-
if (destroyed) return;
|
|
388
|
-
destroyed = true;
|
|
389
|
-
if (stateTimer) clearTimeout(stateTimer);
|
|
390
|
-
keyboard?.destroy();
|
|
391
|
-
unsubTheme();
|
|
392
|
-
unsubNbTheme();
|
|
393
|
-
unsubData();
|
|
394
|
-
unsubAction();
|
|
395
|
-
themeCallbacks.clear();
|
|
396
|
-
dataCallbacks.clear();
|
|
397
|
-
actionCallbacks.clear();
|
|
398
|
-
transport.destroy();
|
|
399
|
-
}
|
|
400
|
-
};
|
|
401
|
-
return synapse;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
exports.createSynapse = createSynapse;
|
|
405
|
-
//# sourceMappingURL=chunk-MQNKIR7K.cjs.map
|
|
406
|
-
//# sourceMappingURL=chunk-MQNKIR7K.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/detection.ts","../src/keyboard.ts","../src/result-parser.ts","../src/transport.ts","../src/core.ts"],"names":["options"],"mappings":"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","file":"chunk-MQNKIR7K.cjs","sourcesContent":["import type { HostInfo, SynapseTheme } from \"./types\";\n\nconst DEFAULT_THEME: SynapseTheme = {\n mode: \"light\",\n primaryColor: \"#6366f1\",\n tokens: {},\n};\n\n/**\n * Detect the host environment from the ext-apps `ui/initialize` response.\n *\n * Handles missing or malformed fields gracefully — never throws.\n */\nexport function detectHost(initResponse: unknown): HostInfo {\n const resp = initResponse as Record<string, unknown> | null | undefined;\n\n const serverInfo = safeObj(resp?.serverInfo);\n const serverName = typeof serverInfo?.name === \"string\" ? serverInfo.name : \"unknown\";\n\n const protocolVersion =\n typeof resp?.protocolVersion === \"string\" ? resp.protocolVersion : \"unknown\";\n\n const hostContext = safeObj(resp?.hostContext);\n const theme = extractTheme(hostContext?.theme);\n\n return {\n isNimbleBrain: serverName === \"nimblebrain\",\n serverName,\n protocolVersion,\n theme,\n };\n}\n\nfunction extractTheme(raw: unknown): SynapseTheme {\n const obj = safeObj(raw);\n if (!obj) return { ...DEFAULT_THEME };\n\n const mode = obj.mode === \"light\" || obj.mode === \"dark\" ? obj.mode : DEFAULT_THEME.mode;\n\n const primaryColor =\n typeof obj.primaryColor === \"string\" ? obj.primaryColor : DEFAULT_THEME.primaryColor;\n\n const tokens =\n obj.tokens !== null && typeof obj.tokens === \"object\" && !Array.isArray(obj.tokens)\n ? (obj.tokens as Record<string, string>)\n : {};\n\n return { mode, primaryColor, tokens };\n}\n\nfunction safeObj(value: unknown): Record<string, unknown> | undefined {\n if (value !== null && typeof value === \"object\" && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n return undefined;\n}\n","import type { SynapseTransport } from \"./transport.js\";\nimport type { KeyForwardConfig } from \"./types.js\";\n\n/**\n * Forward keyboard shortcuts from the iframe document to the host.\n *\n * By default, forwards all Ctrl/Cmd+key combos and Escape.\n * Apps can customize via `forwardKeys` config.\n */\nexport class KeyboardForwarder {\n private listener: (event: KeyboardEvent) => void;\n private destroyed = false;\n\n constructor(transport: SynapseTransport, customKeys?: KeyForwardConfig[]) {\n const config = customKeys ?? null; // null = default behavior\n\n this.listener = (event: KeyboardEvent) => {\n if (this.destroyed) return;\n if (this.shouldForward(event, config)) {\n event.preventDefault();\n transport.send(\"synapse/keydown\", {\n key: event.key,\n ctrlKey: event.ctrlKey,\n metaKey: event.metaKey,\n shiftKey: event.shiftKey,\n altKey: event.altKey,\n });\n }\n };\n\n document.addEventListener(\"keydown\", this.listener);\n }\n\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n document.removeEventListener(\"keydown\", this.listener);\n }\n\n private shouldForward(event: KeyboardEvent, config: KeyForwardConfig[] | null): boolean {\n // Empty array = forwarding disabled\n if (config && config.length === 0) return false;\n\n // Custom config: match exactly\n if (config) {\n return config.some(\n (k) =>\n event.key.toLowerCase() === k.key.toLowerCase() &&\n (k.ctrl === undefined || event.ctrlKey === k.ctrl) &&\n (k.meta === undefined || event.metaKey === k.meta) &&\n (k.shift === undefined || event.shiftKey === k.shift) &&\n (k.alt === undefined || event.altKey === k.alt),\n );\n }\n\n // Default: forward all Ctrl/Cmd combos + Escape,\n // EXCEPT clipboard shortcuts (c, v, x, a) which the browser must handle natively.\n if (event.key === \"Escape\") return true;\n if (event.ctrlKey || event.metaKey) {\n const key = event.key.toLowerCase();\n if (key === \"c\" || key === \"v\" || key === \"x\" || key === \"a\") return false;\n return true;\n }\n return false;\n }\n}\n","import type { ToolCallResult } from \"./types.js\";\n\n/**\n * Normalize a raw tool call response into a consistent `ToolCallResult`.\n *\n * Handles three shapes:\n * 1. MCP `CallToolResult` — has a `content` array with typed blocks.\n * 2. Raw JSON object (NimbleBrain bridge) — used as-is.\n * 3. Null / undefined — returns `{ data: null, isError: false }`.\n */\nexport function parseToolResult(raw: unknown): ToolCallResult {\n if (raw == null) {\n return { data: null, isError: false };\n }\n\n if (isCallToolResult(raw)) {\n return parseCallToolResult(raw);\n }\n\n // Raw JSON object — pass through.\n return { data: raw, isError: false };\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\ninterface McpTextBlock {\n type: \"text\";\n text: string;\n}\n\ninterface McpCallToolResult {\n content: unknown[];\n isError?: boolean;\n}\n\nfunction isCallToolResult(value: unknown): value is McpCallToolResult {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n return false;\n }\n return Array.isArray((value as Record<string, unknown>).content);\n}\n\nfunction isTextBlock(block: unknown): block is McpTextBlock {\n if (block === null || typeof block !== \"object\" || Array.isArray(block)) {\n return false;\n }\n const obj = block as Record<string, unknown>;\n return obj.type === \"text\" && typeof obj.text === \"string\";\n}\n\nfunction parseCallToolResult(result: McpCallToolResult): ToolCallResult {\n const isError = result.isError === true;\n const content = result.content;\n\n if (content.length === 0) {\n return { data: null, isError };\n }\n\n const firstText = content.find(isTextBlock);\n\n if (!firstText) {\n // No text blocks — return the full content array so callers can inspect it.\n return { data: content, isError };\n }\n\n // Try to parse JSON from the text block.\n try {\n return { data: JSON.parse(firstText.text), isError };\n } catch {\n // Invalid JSON — return the raw string.\n return { data: firstText.text, isError };\n }\n}\n","import type {\n JsonRpcMessage,\n JsonRpcNotification,\n JsonRpcRequest,\n JsonRpcResponse,\n} from \"./types.js\";\n\ntype PendingEntry = {\n resolve: (value: unknown) => void;\n reject: (reason: unknown) => void;\n};\n\ntype MessageHandler = (params: Record<string, unknown> | undefined) => void;\n\nexport class SynapseTransport {\n private counter = 0;\n private destroyed = false;\n private pending = new Map<string, PendingEntry>();\n private handlers = new Map<string, Set<MessageHandler>>();\n private listener: (event: MessageEvent) => void;\n\n constructor() {\n this.listener = (event: MessageEvent) => this.handleMessage(event);\n window.addEventListener(\"message\", this.listener);\n }\n\n send(method: string, params?: Record<string, unknown>): void {\n if (this.destroyed) return;\n\n const msg: JsonRpcNotification = {\n jsonrpc: \"2.0\",\n method,\n ...(params !== undefined && { params }),\n };\n window.parent.postMessage(msg, \"*\");\n }\n\n request(method: string, params?: Record<string, unknown>): Promise<unknown> {\n if (this.destroyed) {\n return Promise.reject(new Error(\"Transport destroyed\"));\n }\n\n const id = `syn-${++this.counter}`;\n const msg: JsonRpcRequest = {\n jsonrpc: \"2.0\",\n method,\n id,\n ...(params !== undefined && { params }),\n };\n\n return new Promise<unknown>((resolve, reject) => {\n this.pending.set(id, { resolve, reject });\n window.parent.postMessage(msg, \"*\");\n });\n }\n\n onMessage(method: string, callback: MessageHandler): () => void {\n if (!this.handlers.has(method)) {\n this.handlers.set(method, new Set());\n }\n this.handlers.get(method)?.add(callback);\n\n return () => {\n const set = this.handlers.get(method);\n if (set) {\n set.delete(callback);\n if (set.size === 0) {\n this.handlers.delete(method);\n }\n }\n };\n }\n\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n\n window.removeEventListener(\"message\", this.listener);\n\n const error = new Error(\"Transport destroyed\");\n for (const entry of this.pending.values()) {\n entry.reject(error);\n }\n this.pending.clear();\n this.handlers.clear();\n }\n\n private handleMessage(event: MessageEvent): void {\n if (this.destroyed) return;\n\n const data = event.data as JsonRpcMessage;\n if (!data || data.jsonrpc !== \"2.0\") return;\n\n // Response to a pending request\n if (\"id\" in data && data.id && !(\"method\" in data)) {\n const response = data as JsonRpcResponse;\n const entry = this.pending.get(response.id);\n if (!entry) return;\n this.pending.delete(response.id);\n\n if (response.error) {\n const err = new Error(response.error.message);\n (err as any).code = response.error.code;\n (err as any).data = response.error.data;\n entry.reject(err);\n } else {\n entry.resolve(response.result);\n }\n return;\n }\n\n // Incoming notification\n if (\"method\" in data && !(\"id\" in data && data.id)) {\n const notification = data as JsonRpcNotification;\n const set = this.handlers.get(notification.method);\n if (set) {\n for (const handler of set) {\n handler(notification.params);\n }\n }\n }\n }\n}\n","import { detectHost } from \"./detection.js\";\nimport { KeyboardForwarder } from \"./keyboard.js\";\nimport { parseToolResult } from \"./result-parser.js\";\nimport { SynapseTransport } from \"./transport.js\";\nimport type {\n AgentAction,\n DataChangedEvent,\n FileResult,\n HostInfo,\n RequestFileOptions,\n Synapse,\n SynapseOptions,\n SynapseTheme,\n ToolCallResult,\n} from \"./types.js\";\n\n/**\n * Create a Synapse instance.\n *\n * Wraps the ext-apps protocol handshake via `SynapseTransport` and provides\n * a typed, framework-agnostic API for calling tools, reacting to data changes,\n * dispatching actions, and pushing LLM-visible state.\n *\n * In non-NimbleBrain hosts, NB-specific methods degrade to no-ops.\n */\nexport function createSynapse(options: SynapseOptions): Synapse {\n const { name, version, internal = false, forwardKeys } = options;\n\n const transport = new SynapseTransport();\n let hostInfo: HostInfo | null = null;\n let currentTheme: SynapseTheme = {\n mode: \"light\",\n primaryColor: \"#6366f1\",\n tokens: {},\n };\n let destroyed = false;\n\n // --- Debounce for setVisibleState ---\n let stateTimer: ReturnType<typeof setTimeout> | null = null;\n\n // --- Keyboard forwarding ---\n let keyboard: KeyboardForwarder | null = null;\n\n // --- ext-apps handshake ---\n // We send ui/initialize as a JSON-RPC request and wait for the response.\n const ready = transport\n .request(\"ui/initialize\", {\n protocolVersion: \"2026-01-26\",\n clientInfo: { name, version },\n capabilities: {},\n })\n .then((result) => {\n hostInfo = detectHost(result);\n currentTheme = hostInfo.theme;\n\n // Send initialized notification per ext-apps spec\n transport.send(\"ui/notifications/initialized\", {});\n\n // Set up keyboard forwarding after we know the host\n keyboard = new KeyboardForwarder(transport, forwardKeys);\n });\n\n // Listen for theme changes from the host\n const unsubTheme = transport.onMessage(\"ui/notifications/host-context-changed\", (params) => {\n if (!params) return;\n const mode = params.theme === \"dark\" ? \"dark\" : \"light\";\n const tokens =\n params.tokens && typeof params.tokens === \"object\"\n ? (params.tokens as Record<string, string>)\n : currentTheme.tokens;\n currentTheme = { mode, primaryColor: currentTheme.primaryColor, tokens };\n for (const cb of themeCallbacks) cb(currentTheme);\n });\n\n // Also listen for NB-specific synapse/theme-changed message\n const unsubNbTheme = transport.onMessage(\"synapse/theme-changed\", (params) => {\n if (!params) return;\n const mode =\n params.mode === \"dark\" || params.mode === \"light\" ? params.mode : currentTheme.mode;\n const tokens =\n params.tokens && typeof params.tokens === \"object\"\n ? (params.tokens as Record<string, string>)\n : currentTheme.tokens;\n currentTheme = { mode, primaryColor: currentTheme.primaryColor, tokens };\n for (const cb of themeCallbacks) cb(currentTheme);\n });\n\n const themeCallbacks = new Set<(theme: SynapseTheme) => void>();\n const dataCallbacks = new Set<(event: DataChangedEvent) => void>();\n const actionCallbacks = new Set<(action: AgentAction) => void>();\n\n // Listen for data change events\n const unsubData = transport.onMessage(\"synapse/data-changed\", (params) => {\n if (!params) return;\n const event: DataChangedEvent = {\n source: \"agent\",\n server: (params.server as string) ?? \"\",\n tool: (params.tool as string) ?? \"\",\n };\n for (const cb of dataCallbacks) cb(event);\n });\n\n // Listen for agent actions (typed, declarative commands from the server)\n const unsubAction = transport.onMessage(\"synapse/action\", (params) => {\n if (!params || typeof params.type !== \"string\") return;\n const action: AgentAction = {\n type: params.type as string,\n payload: (params.payload as Record<string, unknown>) ?? {},\n requiresConfirmation: params.requiresConfirmation === true,\n label: typeof params.label === \"string\" ? params.label : undefined,\n };\n for (const cb of actionCallbacks) cb(action);\n });\n\n const isNB = () => hostInfo?.isNimbleBrain === true;\n\n const synapse: Synapse = {\n get ready() {\n return ready;\n },\n\n get isNimbleBrainHost() {\n return isNB();\n },\n\n get destroyed() {\n return destroyed;\n },\n\n async callTool<TInput = Record<string, unknown>, TOutput = unknown>(\n toolName: string,\n args?: TInput,\n ): Promise<ToolCallResult<TOutput>> {\n const params: Record<string, unknown> = {\n name: toolName,\n arguments: args ?? {},\n };\n // Internal apps can cross-call\n if (internal) {\n params.server = name;\n }\n const raw = await transport.request(\"tools/call\", params);\n return parseToolResult(raw) as ToolCallResult<TOutput>;\n },\n\n onDataChanged(callback: (event: DataChangedEvent) => void): () => void {\n dataCallbacks.add(callback);\n return () => {\n dataCallbacks.delete(callback);\n };\n },\n\n onAction(callback: (action: AgentAction) => void): () => void {\n actionCallbacks.add(callback);\n return () => {\n actionCallbacks.delete(callback);\n };\n },\n\n getTheme(): SynapseTheme {\n return { ...currentTheme };\n },\n\n onThemeChanged(callback: (theme: SynapseTheme) => void): () => void {\n themeCallbacks.add(callback);\n return () => {\n themeCallbacks.delete(callback);\n };\n },\n\n action(action: string, params?: Record<string, unknown>): void {\n if (!isNB()) return;\n transport.send(\"synapse/action\", { action, ...params });\n },\n\n chat(message: string, context?: { action?: string; entity?: string }): void {\n const textBlock: Record<string, unknown> = { type: \"text\", text: message };\n if (isNB() && context) {\n textBlock._meta = { context };\n }\n transport.send(\"ui/message\", {\n role: \"user\",\n content: [textBlock],\n });\n },\n\n setVisibleState(state: Record<string, unknown>, summary?: string): void {\n // Debounce: 250ms\n if (stateTimer) clearTimeout(stateTimer);\n stateTimer = setTimeout(() => {\n transport.send(\"ui/update-model-context\", {\n structuredContent: state,\n ...(summary !== undefined && {\n content: [{ type: \"text\", text: summary }],\n }),\n });\n stateTimer = null;\n }, 250);\n },\n\n downloadFile(filename: string, content: string | Blob, mimeType?: string): void {\n if (!isNB()) return;\n const data = typeof content === \"string\" ? content : \"[Blob content not serializable]\";\n transport.send(\"synapse/download-file\", {\n data,\n filename,\n mimeType: mimeType ?? \"application/octet-stream\",\n });\n },\n\n openLink(url: string): void {\n transport.send(\"ui/open-link\", { url });\n if (!isNB()) {\n window.open(url, \"_blank\", \"noopener\");\n }\n },\n\n async requestFile(options?: RequestFileOptions): Promise<FileResult | null> {\n if (!isNB()) {\n throw new Error(\"requestFile is not supported in this host\");\n }\n const result = await transport.request(\"synapse/request-file\", {\n accept: options?.accept,\n maxSize: options?.maxSize ?? 26_214_400,\n multiple: false,\n });\n return (result as FileResult) ?? null;\n },\n\n async requestFiles(options?: RequestFileOptions): Promise<FileResult[]> {\n if (!isNB()) {\n throw new Error(\"requestFiles is not supported in this host\");\n }\n const result = await transport.request(\"synapse/request-file\", {\n accept: options?.accept,\n maxSize: options?.maxSize ?? 26_214_400,\n multiple: true,\n });\n if (!result) return [];\n return Array.isArray(result) ? (result as FileResult[]) : [result as FileResult];\n },\n\n _onMessage(\n method: string,\n callback: (params: Record<string, unknown> | undefined) => void,\n ): () => void {\n return transport.onMessage(method, callback);\n },\n\n _request(method: string, params?: Record<string, unknown>): Promise<unknown> {\n return transport.request(method, params);\n },\n\n destroy(): void {\n if (destroyed) return;\n destroyed = true;\n\n if (stateTimer) clearTimeout(stateTimer);\n keyboard?.destroy();\n unsubTheme();\n unsubNbTheme();\n unsubData();\n unsubAction();\n themeCallbacks.clear();\n dataCallbacks.clear();\n actionCallbacks.clear();\n transport.destroy();\n },\n };\n\n return synapse;\n}\n"]}
|