@nimblebrain/synapse 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +32 -37
  2. package/dist/check-JEM5PN64.cjs +435 -0
  3. package/dist/check-JEM5PN64.cjs.map +1 -0
  4. package/dist/check-YDZUQT6A.js +429 -0
  5. package/dist/check-YDZUQT6A.js.map +1 -0
  6. package/dist/{chunk-VDQZQDU3.cjs → chunk-BNF352SA.cjs} +56 -145
  7. package/dist/chunk-BNF352SA.cjs.map +1 -0
  8. package/dist/{chunk-HKRTDGXN.js → chunk-DJS2FKBN.js} +21 -89
  9. package/dist/chunk-DJS2FKBN.js.map +1 -0
  10. package/dist/chunk-H3INUKA3.js +32 -0
  11. package/dist/chunk-H3INUKA3.js.map +1 -0
  12. package/dist/{chunk-FB7GPBEM.cjs → chunk-HQLSVUOR.cjs} +193 -314
  13. package/dist/chunk-HQLSVUOR.cjs.map +1 -0
  14. package/dist/chunk-ISV6HFQE.cjs +40 -0
  15. package/dist/chunk-ISV6HFQE.cjs.map +1 -0
  16. package/dist/{chunk-3YSXPBEQ.js → chunk-TR2YNWZ5.js} +190 -313
  17. package/dist/chunk-TR2YNWZ5.js.map +1 -0
  18. package/dist/{chunk-ZLASWV4N.js → chunk-UKP4W6DI.js} +56 -145
  19. package/dist/chunk-UKP4W6DI.js.map +1 -0
  20. package/dist/{chunk-LPWEQCZV.cjs → chunk-YSL7KJNU.cjs} +21 -92
  21. package/dist/chunk-YSL7KJNU.cjs.map +1 -0
  22. package/dist/codegen/cli.cjs +38 -1
  23. package/dist/codegen/cli.cjs.map +1 -1
  24. package/dist/codegen/cli.js +38 -1
  25. package/dist/codegen/cli.js.map +1 -1
  26. package/dist/codegen/index.d.cts +1 -1
  27. package/dist/codegen/index.d.ts +1 -1
  28. package/dist/connect.iife.global.js +66 -43
  29. package/dist/{detect-BHYg26_d.d.cts → detect-DVGaL2bH.d.cts} +39 -33
  30. package/dist/{detect-Bf8Q_0dK.d.ts → detect-DVGaL2bH.d.ts} +39 -33
  31. package/dist/host/index.cjs +17 -17
  32. package/dist/host/index.d.cts +11 -27
  33. package/dist/host/index.d.ts +11 -27
  34. package/dist/host/index.js +2 -2
  35. package/dist/index.cjs +52 -17
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +104 -31
  38. package/dist/index.d.ts +104 -31
  39. package/dist/index.js +34 -3
  40. package/dist/index.js.map +1 -1
  41. package/dist/react/index.cjs +11 -10
  42. package/dist/react/index.cjs.map +1 -1
  43. package/dist/react/index.d.cts +49 -12
  44. package/dist/react/index.d.ts +49 -12
  45. package/dist/react/index.js +6 -5
  46. package/dist/react/index.js.map +1 -1
  47. package/dist/{server-QIYIJHD5.cjs → server-BR5MNFOB.cjs} +40 -29
  48. package/dist/server-BR5MNFOB.cjs.map +1 -0
  49. package/dist/{server-5N76YCWC.js → server-OOEEWAEG.js} +40 -30
  50. package/dist/server-OOEEWAEG.js.map +1 -0
  51. package/dist/synapse-runtime.iife.global.js +66 -43
  52. package/dist/synapse-ui.iife.global.js +4 -4
  53. package/dist/{types-uEO4VFJ2.d.cts → types-BxPfGHKO.d.cts} +48 -125
  54. package/dist/{types-uEO4VFJ2.d.ts → types-BxPfGHKO.d.ts} +48 -125
  55. package/dist/vite/index.cjs +77 -33
  56. package/dist/vite/index.cjs.map +1 -1
  57. package/dist/vite/index.d.cts +5 -1
  58. package/dist/vite/index.d.ts +5 -1
  59. package/dist/vite/index.js +77 -33
  60. package/dist/vite/index.js.map +1 -1
  61. package/package.json +12 -6
  62. package/dist/chunk-3YSXPBEQ.js.map +0 -1
  63. package/dist/chunk-FB7GPBEM.cjs.map +0 -1
  64. package/dist/chunk-HKRTDGXN.js.map +0 -1
  65. package/dist/chunk-LPWEQCZV.cjs.map +0 -1
  66. package/dist/chunk-VDQZQDU3.cjs.map +0 -1
  67. package/dist/chunk-ZLASWV4N.js.map +0 -1
  68. package/dist/server-5N76YCWC.js.map +0 -1
  69. package/dist/server-QIYIJHD5.cjs.map +0 -1
package/dist/index.d.cts CHANGED
@@ -1,60 +1,132 @@
1
1
  export { CreateTaskResult, ReadResourceRequest, ReadResourceResult, Task, TaskStatus } from '@modelcontextprotocol/sdk/types.js';
2
- import { C as ConnectOptions, A as App, R as RequestFileOptions, F as FileResult, a as CallToolAsTaskOptions, T as TaskHandle } from './types-uEO4VFJ2.cjs';
3
- export { h as AgentAction, i as AppEventName, B as BuiltinActionType, c as CallToolOptions, D as DataChangedEvent, j as Dimensions, k as FontDisplayValue, g as FontFaceDescriptor, H as HostInfo, K as KeyForwardConfig, M as ModelContext, N as NavigatePayload, l as NotifyPayload, m as TasksCapability, d as Theme, b as ToolCallResult, f as ToolDefinition, e as ToolResultData } from './types-uEO4VFJ2.cjs';
4
- export { C as ConnectUIOptions, H as HostCapabilities, a as HostKind, b as HostUnsupportedError, S as SYNAPSE_DATA_ELEMENT_ID, c as SynapseUIClient, d as SynapseUITheme, e as connectUI, f as detectHostKind } from './detect-BHYg26_d.cjs';
2
+ import { C as ConnectOptions, A as App, R as RequestFileOptions, F as FileResult, a as CallToolAsTaskOptions, T as TaskHandle } from './types-BxPfGHKO.cjs';
3
+ export { f as AppEventName, D as Dimensions, K as KeyForwardConfig, M as ModelContext, g as TasksCapability, c as Theme, b as ToolCallResult, e as ToolDefinition, d as ToolResultData } from './types-BxPfGHKO.cjs';
4
+ import { McpUiDownloadFileResult } from '@modelcontextprotocol/ext-apps';
5
5
  export { McpUiHostContext } from '@modelcontextprotocol/ext-apps';
6
+ export { C as ConnectUIOptions, a as HostCapabilities, b as HostKind, c as HostUnsupportedError, d as SYNAPSE_DATA_ELEMENT_ID, e as SynapseUIClient, S as SynapseUITheme, T as ToolCallError, g as connectUI, h as detectHostKind } from './detect-DVGaL2bH.cjs';
6
7
 
7
8
  /**
8
9
  * Connect to an MCP Apps host.
9
10
  *
10
- * The one entry point: owns the ext-apps handshake, theme injection, content
11
- * parsing, resize management, and event routing, and resolves to a ready
12
- * {@link App}.
11
+ * The one entry point. The protocol underneath is the spec's own client,
12
+ * `@modelcontextprotocol/ext-apps`'s `App`: it owns the transport, the
13
+ * handshake and the wire schemas. What this adds is the framework on top —
14
+ * theme injection, the parsed payloads, multi-subscriber events, resize, and
15
+ * the NimbleBrain extensions — and it resolves to a ready {@link App}.
13
16
  *
14
17
  * The `App` it returns stays deliberately small. NimbleBrain's own extensions
15
- * (`action`, the file picker, `downloadFile`) and the MCP tasks utility are
18
+ * (`action`, the file picker), `downloadFile` and the MCP tasks utility are
16
19
  * composable functions over it — import them from the package root.
17
20
  */
18
21
  declare function connect(options: ConnectOptions): Promise<App>;
19
22
 
20
23
  /**
21
- * Host-context key carrying `@font-face` descriptors.
22
- *
23
- * A NimbleBrain extension (`synapse/` prefix), because the ext-apps spec has no
24
- * equivalent: `styles.variables` is a flat `Record<McpUiStyleVariableKey, string>`
25
- * that can name a font family but cannot carry the rule that loads it.
26
- * `McpUiHostContext` declares `[key: string]: unknown` for forward
27
- * compatibility, so riding an extra key is spec-legal, and hosts that don't send
28
- * it degrade to the web-safe token fallbacks.
29
- */
30
- declare const FONT_FACES_CONTEXT_KEY = "synapse/fontFaces";
31
-
32
- /**
33
- * Trigger a host-side action.
24
+ * Hand the user a file to save, over the ext-apps `ui/download-file` request.
34
25
  *
35
- * The inverse direction of the `action` event: this sends a command *to* the
36
- * host (navigate, open a panel), where `app.on("action", …)` receives the ones
37
- * a tool emits. No-op off a NimbleBrain host.
26
+ * A function over {@link App} rather than a method on it, like the helpers in
27
+ * `extensions.ts` — but unlike them this is spec surface, so it is not gated on
28
+ * the host identity. Any host that advertises `downloadFile` can answer it.
29
+ *
30
+ * The bytes always travel embedded. The spec also allows a `ResourceLink`, which
31
+ * asks the host to fetch a URI the app names; a host holding the user's session
32
+ * has every reason to refuse that, and the NimbleBrain host does.
38
33
  */
39
- declare function action(app: App, name: string, params?: Record<string, unknown>): void;
34
+
40
35
  /**
41
36
  * Hand the user a file to save.
42
37
  *
38
+ * A string is sent as the resource's `text`; a `Blob` is read and sent as
39
+ * base64 `blob`. Reading a `Blob` is asynchronous, which is why this returns a
40
+ * promise.
41
+ *
43
42
  * Precedence for the MIME type: the explicit argument, then the Blob's own
44
43
  * type, then `application/octet-stream`. An empty-string argument falls
45
44
  * through — a `""` MIME is effectively "no type".
45
+ *
46
+ * The filename is the last segment of the resource's `file:///` URI, because an
47
+ * embedded resource has no name field of its own.
48
+ *
49
+ * Resolves with the host's result: `{ isError: true }` when the host declined
50
+ * or the user cancelled. Rejects with `HostCapabilityError`, without sending,
51
+ * when the host did not declare `downloadFile` — a host that does not implement
52
+ * the request may never answer it, and a request has no deadline.
53
+ */
54
+ declare function downloadFile(app: App, filename: string, content: string | Blob, mimeType?: string): Promise<McpUiDownloadFileResult>;
55
+
56
+ /**
57
+ * Thrown when an app asks for something the host did not declare in
58
+ * `ui/initialize`.
59
+ *
60
+ * The request is never sent. A host that does not implement a request may never
61
+ * answer it, and this SDK's requests carry no deadline, so sending anyway would
62
+ * leave the caller waiting forever rather than failing.
63
+ *
64
+ * `capability` names what was missing, as the host would have declared it: a
65
+ * `hostCapabilities` field (`serverTools`, `downloadFile`) or an extension
66
+ * identifier (`ai.nimblebrain/request-file`, `io.modelcontextprotocol/tasks`).
67
+ */
68
+ declare class HostCapabilityError extends Error {
69
+ readonly capability: string;
70
+ constructor(feature: string, capability: string);
71
+ }
72
+
73
+ /**
74
+ * The NimbleBrain host extensions. This is the complete list; anything else an
75
+ * app sends is spec.
76
+ *
77
+ * Each extension has one name, used both as its method and as the identifier a
78
+ * host declares in `hostCapabilities.experimental` to offer it. The ext-apps
79
+ * host capability type has no field for extensions, and a spec client parses
80
+ * the `ui/initialize` result against that type, so `experimental` is the one
81
+ * slot whose contents reach the app. The MCP tasks capability travels the same
82
+ * way, for the same reason.
83
+ *
84
+ * The gate is the declaration, not the host's name. A host that implements an
85
+ * extension says so; a host that does not is never sent it.
86
+ */
87
+ declare const NIMBLEBRAIN_EXTENSIONS: {
88
+ /** App → host notification: run a host action (navigate, open a panel). */
89
+ readonly action: {
90
+ readonly method: "ai.nimblebrain/action";
91
+ readonly capability: "ai.nimblebrain/action";
92
+ };
93
+ /** App → host request: the host's file picker, answered `{ files }`. */
94
+ readonly requestFile: {
95
+ readonly method: "ai.nimblebrain/request-file";
96
+ readonly capability: "ai.nimblebrain/request-file";
97
+ };
98
+ /** App → host notification: a keyboard shortcut pressed inside the frame. */
99
+ readonly keydown: {
100
+ readonly method: "ai.nimblebrain/keydown";
101
+ readonly capability: "ai.nimblebrain/keydown";
102
+ };
103
+ };
104
+ type NimbleBrainExtension = keyof typeof NIMBLEBRAIN_EXTENSIONS;
105
+
106
+ /**
107
+ * Whether the host declared a NimbleBrain extension. Use it to decide what to
108
+ * offer, e.g. to hide an upload button on a host with no file picker.
46
109
  */
47
- declare function downloadFile(app: App, filename: string, content: string | Blob, mimeType?: string): void;
110
+ declare function hostSupports(app: App, extension: NimbleBrainExtension): boolean;
111
+ /**
112
+ * Trigger a host-side action.
113
+ *
114
+ * Sends a command *to* the host (navigate, open a panel). A no-op when the host
115
+ * did not declare `ai.nimblebrain/action`.
116
+ */
117
+ declare function action(app: App, name: string, params?: Record<string, unknown>): void;
48
118
  /**
49
119
  * Request one file from the user via the host's native file picker.
50
120
  *
51
- * Resolves `null` if the user cancels. Throws off a NimbleBrain host.
121
+ * Resolves `null` if the user cancels. Rejects with `HostCapabilityError` when
122
+ * the host did not declare `ai.nimblebrain/request-file`.
52
123
  */
53
124
  declare function pickFile(app: App, options?: RequestFileOptions): Promise<FileResult | null>;
54
125
  /**
55
126
  * Request several files from the user.
56
127
  *
57
- * Resolves `[]` if the user cancels. Throws off a NimbleBrain host.
128
+ * Resolves `[]` if the user cancels. Rejects with `HostCapabilityError` when
129
+ * the host did not declare `ai.nimblebrain/request-file`.
58
130
  */
59
131
  declare function pickFiles(app: App, options?: RequestFileOptions): Promise<FileResult[]>;
60
132
 
@@ -72,10 +144,11 @@ declare function pickFiles(app: App, options?: RequestFileOptions): Promise<File
72
144
  * const result = await handle.result();
73
145
  * ```
74
146
  *
75
- * Throws if the host did not advertise `tasks.requests.tools.call` — per spec
76
- * a requestor MUST NOT task-augment without matching receiver capability.
77
- * Fall back to `app.callTool`.
147
+ * Rejects with `HostCapabilityError` if the host did not advertise
148
+ * `tasks.requests.tools.call` — per spec a requestor MUST NOT task-augment
149
+ * without matching receiver capability. Check `app.supportsTasks` first, and
150
+ * fall back to `app.callTool`.
78
151
  */
79
152
  declare function callToolAsTask<TOutput = unknown>(app: App, toolName: string, args?: unknown, options?: CallToolAsTaskOptions): Promise<TaskHandle<TOutput>>;
80
153
 
81
- export { App, CallToolAsTaskOptions, ConnectOptions, FONT_FACES_CONTEXT_KEY, FileResult, RequestFileOptions, TaskHandle, action, callToolAsTask, connect, downloadFile, pickFile, pickFiles };
154
+ export { App, CallToolAsTaskOptions, ConnectOptions, FileResult, HostCapabilityError, NIMBLEBRAIN_EXTENSIONS, type NimbleBrainExtension, RequestFileOptions, TaskHandle, action, callToolAsTask, connect, downloadFile, hostSupports, pickFile, pickFiles };
package/dist/index.d.ts CHANGED
@@ -1,60 +1,132 @@
1
1
  export { CreateTaskResult, ReadResourceRequest, ReadResourceResult, Task, TaskStatus } from '@modelcontextprotocol/sdk/types.js';
2
- import { C as ConnectOptions, A as App, R as RequestFileOptions, F as FileResult, a as CallToolAsTaskOptions, T as TaskHandle } from './types-uEO4VFJ2.js';
3
- export { h as AgentAction, i as AppEventName, B as BuiltinActionType, c as CallToolOptions, D as DataChangedEvent, j as Dimensions, k as FontDisplayValue, g as FontFaceDescriptor, H as HostInfo, K as KeyForwardConfig, M as ModelContext, N as NavigatePayload, l as NotifyPayload, m as TasksCapability, d as Theme, b as ToolCallResult, f as ToolDefinition, e as ToolResultData } from './types-uEO4VFJ2.js';
4
- export { C as ConnectUIOptions, H as HostCapabilities, a as HostKind, b as HostUnsupportedError, S as SYNAPSE_DATA_ELEMENT_ID, c as SynapseUIClient, d as SynapseUITheme, e as connectUI, f as detectHostKind } from './detect-Bf8Q_0dK.js';
2
+ import { C as ConnectOptions, A as App, R as RequestFileOptions, F as FileResult, a as CallToolAsTaskOptions, T as TaskHandle } from './types-BxPfGHKO.js';
3
+ export { f as AppEventName, D as Dimensions, K as KeyForwardConfig, M as ModelContext, g as TasksCapability, c as Theme, b as ToolCallResult, e as ToolDefinition, d as ToolResultData } from './types-BxPfGHKO.js';
4
+ import { McpUiDownloadFileResult } from '@modelcontextprotocol/ext-apps';
5
5
  export { McpUiHostContext } from '@modelcontextprotocol/ext-apps';
6
+ export { C as ConnectUIOptions, a as HostCapabilities, b as HostKind, c as HostUnsupportedError, d as SYNAPSE_DATA_ELEMENT_ID, e as SynapseUIClient, S as SynapseUITheme, T as ToolCallError, g as connectUI, h as detectHostKind } from './detect-DVGaL2bH.js';
6
7
 
7
8
  /**
8
9
  * Connect to an MCP Apps host.
9
10
  *
10
- * The one entry point: owns the ext-apps handshake, theme injection, content
11
- * parsing, resize management, and event routing, and resolves to a ready
12
- * {@link App}.
11
+ * The one entry point. The protocol underneath is the spec's own client,
12
+ * `@modelcontextprotocol/ext-apps`'s `App`: it owns the transport, the
13
+ * handshake and the wire schemas. What this adds is the framework on top —
14
+ * theme injection, the parsed payloads, multi-subscriber events, resize, and
15
+ * the NimbleBrain extensions — and it resolves to a ready {@link App}.
13
16
  *
14
17
  * The `App` it returns stays deliberately small. NimbleBrain's own extensions
15
- * (`action`, the file picker, `downloadFile`) and the MCP tasks utility are
18
+ * (`action`, the file picker), `downloadFile` and the MCP tasks utility are
16
19
  * composable functions over it — import them from the package root.
17
20
  */
18
21
  declare function connect(options: ConnectOptions): Promise<App>;
19
22
 
20
23
  /**
21
- * Host-context key carrying `@font-face` descriptors.
22
- *
23
- * A NimbleBrain extension (`synapse/` prefix), because the ext-apps spec has no
24
- * equivalent: `styles.variables` is a flat `Record<McpUiStyleVariableKey, string>`
25
- * that can name a font family but cannot carry the rule that loads it.
26
- * `McpUiHostContext` declares `[key: string]: unknown` for forward
27
- * compatibility, so riding an extra key is spec-legal, and hosts that don't send
28
- * it degrade to the web-safe token fallbacks.
29
- */
30
- declare const FONT_FACES_CONTEXT_KEY = "synapse/fontFaces";
31
-
32
- /**
33
- * Trigger a host-side action.
24
+ * Hand the user a file to save, over the ext-apps `ui/download-file` request.
34
25
  *
35
- * The inverse direction of the `action` event: this sends a command *to* the
36
- * host (navigate, open a panel), where `app.on("action", …)` receives the ones
37
- * a tool emits. No-op off a NimbleBrain host.
26
+ * A function over {@link App} rather than a method on it, like the helpers in
27
+ * `extensions.ts` — but unlike them this is spec surface, so it is not gated on
28
+ * the host identity. Any host that advertises `downloadFile` can answer it.
29
+ *
30
+ * The bytes always travel embedded. The spec also allows a `ResourceLink`, which
31
+ * asks the host to fetch a URI the app names; a host holding the user's session
32
+ * has every reason to refuse that, and the NimbleBrain host does.
38
33
  */
39
- declare function action(app: App, name: string, params?: Record<string, unknown>): void;
34
+
40
35
  /**
41
36
  * Hand the user a file to save.
42
37
  *
38
+ * A string is sent as the resource's `text`; a `Blob` is read and sent as
39
+ * base64 `blob`. Reading a `Blob` is asynchronous, which is why this returns a
40
+ * promise.
41
+ *
43
42
  * Precedence for the MIME type: the explicit argument, then the Blob's own
44
43
  * type, then `application/octet-stream`. An empty-string argument falls
45
44
  * through — a `""` MIME is effectively "no type".
45
+ *
46
+ * The filename is the last segment of the resource's `file:///` URI, because an
47
+ * embedded resource has no name field of its own.
48
+ *
49
+ * Resolves with the host's result: `{ isError: true }` when the host declined
50
+ * or the user cancelled. Rejects with `HostCapabilityError`, without sending,
51
+ * when the host did not declare `downloadFile` — a host that does not implement
52
+ * the request may never answer it, and a request has no deadline.
53
+ */
54
+ declare function downloadFile(app: App, filename: string, content: string | Blob, mimeType?: string): Promise<McpUiDownloadFileResult>;
55
+
56
+ /**
57
+ * Thrown when an app asks for something the host did not declare in
58
+ * `ui/initialize`.
59
+ *
60
+ * The request is never sent. A host that does not implement a request may never
61
+ * answer it, and this SDK's requests carry no deadline, so sending anyway would
62
+ * leave the caller waiting forever rather than failing.
63
+ *
64
+ * `capability` names what was missing, as the host would have declared it: a
65
+ * `hostCapabilities` field (`serverTools`, `downloadFile`) or an extension
66
+ * identifier (`ai.nimblebrain/request-file`, `io.modelcontextprotocol/tasks`).
67
+ */
68
+ declare class HostCapabilityError extends Error {
69
+ readonly capability: string;
70
+ constructor(feature: string, capability: string);
71
+ }
72
+
73
+ /**
74
+ * The NimbleBrain host extensions. This is the complete list; anything else an
75
+ * app sends is spec.
76
+ *
77
+ * Each extension has one name, used both as its method and as the identifier a
78
+ * host declares in `hostCapabilities.experimental` to offer it. The ext-apps
79
+ * host capability type has no field for extensions, and a spec client parses
80
+ * the `ui/initialize` result against that type, so `experimental` is the one
81
+ * slot whose contents reach the app. The MCP tasks capability travels the same
82
+ * way, for the same reason.
83
+ *
84
+ * The gate is the declaration, not the host's name. A host that implements an
85
+ * extension says so; a host that does not is never sent it.
86
+ */
87
+ declare const NIMBLEBRAIN_EXTENSIONS: {
88
+ /** App → host notification: run a host action (navigate, open a panel). */
89
+ readonly action: {
90
+ readonly method: "ai.nimblebrain/action";
91
+ readonly capability: "ai.nimblebrain/action";
92
+ };
93
+ /** App → host request: the host's file picker, answered `{ files }`. */
94
+ readonly requestFile: {
95
+ readonly method: "ai.nimblebrain/request-file";
96
+ readonly capability: "ai.nimblebrain/request-file";
97
+ };
98
+ /** App → host notification: a keyboard shortcut pressed inside the frame. */
99
+ readonly keydown: {
100
+ readonly method: "ai.nimblebrain/keydown";
101
+ readonly capability: "ai.nimblebrain/keydown";
102
+ };
103
+ };
104
+ type NimbleBrainExtension = keyof typeof NIMBLEBRAIN_EXTENSIONS;
105
+
106
+ /**
107
+ * Whether the host declared a NimbleBrain extension. Use it to decide what to
108
+ * offer, e.g. to hide an upload button on a host with no file picker.
46
109
  */
47
- declare function downloadFile(app: App, filename: string, content: string | Blob, mimeType?: string): void;
110
+ declare function hostSupports(app: App, extension: NimbleBrainExtension): boolean;
111
+ /**
112
+ * Trigger a host-side action.
113
+ *
114
+ * Sends a command *to* the host (navigate, open a panel). A no-op when the host
115
+ * did not declare `ai.nimblebrain/action`.
116
+ */
117
+ declare function action(app: App, name: string, params?: Record<string, unknown>): void;
48
118
  /**
49
119
  * Request one file from the user via the host's native file picker.
50
120
  *
51
- * Resolves `null` if the user cancels. Throws off a NimbleBrain host.
121
+ * Resolves `null` if the user cancels. Rejects with `HostCapabilityError` when
122
+ * the host did not declare `ai.nimblebrain/request-file`.
52
123
  */
53
124
  declare function pickFile(app: App, options?: RequestFileOptions): Promise<FileResult | null>;
54
125
  /**
55
126
  * Request several files from the user.
56
127
  *
57
- * Resolves `[]` if the user cancels. Throws off a NimbleBrain host.
128
+ * Resolves `[]` if the user cancels. Rejects with `HostCapabilityError` when
129
+ * the host did not declare `ai.nimblebrain/request-file`.
58
130
  */
59
131
  declare function pickFiles(app: App, options?: RequestFileOptions): Promise<FileResult[]>;
60
132
 
@@ -72,10 +144,11 @@ declare function pickFiles(app: App, options?: RequestFileOptions): Promise<File
72
144
  * const result = await handle.result();
73
145
  * ```
74
146
  *
75
- * Throws if the host did not advertise `tasks.requests.tools.call` — per spec
76
- * a requestor MUST NOT task-augment without matching receiver capability.
77
- * Fall back to `app.callTool`.
147
+ * Rejects with `HostCapabilityError` if the host did not advertise
148
+ * `tasks.requests.tools.call` — per spec a requestor MUST NOT task-augment
149
+ * without matching receiver capability. Check `app.supportsTasks` first, and
150
+ * fall back to `app.callTool`.
78
151
  */
79
152
  declare function callToolAsTask<TOutput = unknown>(app: App, toolName: string, args?: unknown, options?: CallToolAsTaskOptions): Promise<TaskHandle<TOutput>>;
80
153
 
81
- export { App, CallToolAsTaskOptions, ConnectOptions, FONT_FACES_CONTEXT_KEY, FileResult, RequestFileOptions, TaskHandle, action, callToolAsTask, connect, downloadFile, pickFile, pickFiles };
154
+ export { App, CallToolAsTaskOptions, ConnectOptions, FileResult, HostCapabilityError, NIMBLEBRAIN_EXTENSIONS, type NimbleBrainExtension, RequestFileOptions, TaskHandle, action, callToolAsTask, connect, downloadFile, hostSupports, pickFile, pickFiles };
package/dist/index.js CHANGED
@@ -1,5 +1,36 @@
1
- export { HostUnsupportedError, SYNAPSE_DATA_ELEMENT_ID, connectUI, detectHostKind } from './chunk-ZLASWV4N.js';
2
- export { action, callToolAsTask, connect, downloadFile, pickFile, pickFiles } from './chunk-3YSXPBEQ.js';
3
- export { FONT_FACES_CONTEXT_KEY } from './chunk-HKRTDGXN.js';
1
+ export { HostUnsupportedError, SYNAPSE_DATA_ELEMENT_ID, ToolCallError, connectUI, detectHostKind } from './chunk-UKP4W6DI.js';
2
+ import { HostCapabilityError, internalsFor } from './chunk-TR2YNWZ5.js';
3
+ export { HostCapabilityError, action, callToolAsTask, connect, hostSupports, pickFile, pickFiles } from './chunk-TR2YNWZ5.js';
4
+ import './chunk-DJS2FKBN.js';
5
+ export { NIMBLEBRAIN_EXTENSIONS } from './chunk-H3INUKA3.js';
6
+ import { DOWNLOAD_FILE_METHOD } from '@modelcontextprotocol/ext-apps';
7
+
8
+ var BASE64_CHUNK = 32768;
9
+ async function downloadFile(app, filename, content, mimeType) {
10
+ if (!app.hostCapabilities.downloadFile) {
11
+ throw new HostCapabilityError("downloadFile", "downloadFile");
12
+ }
13
+ const internals = internalsFor(app);
14
+ const resolvedMime = mimeType || (content instanceof Blob ? content.type : "") || "application/octet-stream";
15
+ const uri = `file:///${filename}`;
16
+ const resource = typeof content === "string" ? { uri, mimeType: resolvedMime, text: content } : { uri, mimeType: resolvedMime, blob: await toBase64(content) };
17
+ const params = {
18
+ contents: [{ type: "resource", resource }]
19
+ };
20
+ return await internals.request(
21
+ DOWNLOAD_FILE_METHOD,
22
+ params
23
+ );
24
+ }
25
+ async function toBase64(blob) {
26
+ const bytes = new Uint8Array(await blob.arrayBuffer());
27
+ let binary = "";
28
+ for (let i = 0; i < bytes.length; i += BASE64_CHUNK) {
29
+ binary += String.fromCharCode(...bytes.subarray(i, i + BASE64_CHUNK));
30
+ }
31
+ return btoa(binary);
32
+ }
33
+
34
+ export { downloadFile };
4
35
  //# sourceMappingURL=index.js.map
5
36
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
1
+ {"version":3,"sources":["../src/download-file.ts"],"names":[],"mappings":";;;;;;;AAsBA,IAAM,YAAA,GAAe,KAAA;AAqBrB,eAAsB,YAAA,CACpB,GAAA,EACA,QAAA,EACA,OAAA,EACA,QAAA,EACkC;AAClC,EAAA,IAAI,CAAC,GAAA,CAAI,gBAAA,CAAiB,YAAA,EAAc;AACtC,IAAA,MAAM,IAAI,mBAAA,CAAoB,cAAA,EAAgB,cAAc,CAAA;AAAA,EAC9D;AACA,EAAA,MAAM,SAAA,GAAY,aAAa,GAAG,CAAA;AAClC,EAAA,MAAM,eACJ,QAAA,KAAa,OAAA,YAAmB,IAAA,GAAO,OAAA,CAAQ,OAAO,EAAA,CAAA,IAAO,0BAAA;AAC/D,EAAA,MAAM,GAAA,GAAM,WAAW,QAAQ,CAAA,CAAA;AAC/B,EAAA,MAAM,WACJ,OAAO,OAAA,KAAY,WACf,EAAE,GAAA,EAAK,UAAU,YAAA,EAAc,IAAA,EAAM,SAAQ,GAC7C,EAAE,KAAK,QAAA,EAAU,YAAA,EAAc,MAAM,MAAM,QAAA,CAAS,OAAO,CAAA,EAAE;AACnE,EAAA,MAAM,MAAA,GAA6C;AAAA,IACjD,UAAU,CAAC,EAAE,IAAA,EAAM,UAAA,EAAY,UAAU;AAAA,GAC3C;AACA,EAAA,OAAQ,MAAM,SAAA,CAAU,OAAA;AAAA,IACtB,oBAAA;AAAA,IACA;AAAA,GACF;AACF;AAEA,eAAe,SAAS,IAAA,EAA6B;AACnD,EAAA,MAAM,QAAQ,IAAI,UAAA,CAAW,MAAM,IAAA,CAAK,aAAa,CAAA;AACrD,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,MAAA,EAAQ,KAAK,YAAA,EAAc;AACnD,IAAA,MAAA,IAAU,MAAA,CAAO,aAAa,GAAG,KAAA,CAAM,SAAS,CAAA,EAAG,CAAA,GAAI,YAAY,CAAC,CAAA;AAAA,EACtE;AACA,EAAA,OAAO,KAAK,MAAM,CAAA;AACpB","file":"index.js","sourcesContent":["/**\n * Hand the user a file to save, over the ext-apps `ui/download-file` request.\n *\n * A function over {@link App} rather than a method on it, like the helpers in\n * `extensions.ts` — but unlike them this is spec surface, so it is not gated on\n * the host identity. Any host that advertises `downloadFile` can answer it.\n *\n * The bytes always travel embedded. The spec also allows a `ResourceLink`, which\n * asks the host to fetch a URI the app names; a host holding the user's session\n * has every reason to refuse that, and the NimbleBrain host does.\n */\nimport {\n DOWNLOAD_FILE_METHOD,\n type McpUiDownloadFileRequest,\n type McpUiDownloadFileResult,\n} from \"@modelcontextprotocol/ext-apps\";\nimport type { EmbeddedResource } from \"@modelcontextprotocol/sdk/types.js\";\nimport { HostCapabilityError } from \"./errors.js\";\nimport { internalsFor } from \"./internals.js\";\nimport type { App } from \"./types.js\";\n\n/** Bytes per `String.fromCharCode` spread — well under any engine's argument limit. */\nconst BASE64_CHUNK = 0x8000;\n\n/**\n * Hand the user a file to save.\n *\n * A string is sent as the resource's `text`; a `Blob` is read and sent as\n * base64 `blob`. Reading a `Blob` is asynchronous, which is why this returns a\n * promise.\n *\n * Precedence for the MIME type: the explicit argument, then the Blob's own\n * type, then `application/octet-stream`. An empty-string argument falls\n * through — a `\"\"` MIME is effectively \"no type\".\n *\n * The filename is the last segment of the resource's `file:///` URI, because an\n * embedded resource has no name field of its own.\n *\n * Resolves with the host's result: `{ isError: true }` when the host declined\n * or the user cancelled. Rejects with `HostCapabilityError`, without sending,\n * when the host did not declare `downloadFile` — a host that does not implement\n * the request may never answer it, and a request has no deadline.\n */\nexport async function downloadFile(\n app: App,\n filename: string,\n content: string | Blob,\n mimeType?: string,\n): Promise<McpUiDownloadFileResult> {\n if (!app.hostCapabilities.downloadFile) {\n throw new HostCapabilityError(\"downloadFile\", \"downloadFile\");\n }\n const internals = internalsFor(app);\n const resolvedMime =\n mimeType || (content instanceof Blob ? content.type : \"\") || \"application/octet-stream\";\n const uri = `file:///${filename}`;\n const resource: EmbeddedResource[\"resource\"] =\n typeof content === \"string\"\n ? { uri, mimeType: resolvedMime, text: content }\n : { uri, mimeType: resolvedMime, blob: await toBase64(content) };\n const params: McpUiDownloadFileRequest[\"params\"] = {\n contents: [{ type: \"resource\", resource }],\n };\n return (await internals.request(\n DOWNLOAD_FILE_METHOD,\n params as unknown as Record<string, unknown>,\n )) as McpUiDownloadFileResult;\n}\n\nasync function toBase64(blob: Blob): Promise<string> {\n const bytes = new Uint8Array(await blob.arrayBuffer());\n let binary = \"\";\n for (let i = 0; i < bytes.length; i += BASE64_CHUNK) {\n binary += String.fromCharCode(...bytes.subarray(i, i + BASE64_CHUNK));\n }\n return btoa(binary);\n}\n"]}
@@ -1,7 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var chunkFB7GPBEM_cjs = require('../chunk-FB7GPBEM.cjs');
4
- require('../chunk-LPWEQCZV.cjs');
3
+ var chunkHQLSVUOR_cjs = require('../chunk-HQLSVUOR.cjs');
4
+ require('../chunk-YSL7KJNU.cjs');
5
+ var chunkISV6HFQE_cjs = require('../chunk-ISV6HFQE.cjs');
5
6
  var react = require('react');
6
7
  var jsxRuntime = require('react/jsx-runtime');
7
8
 
@@ -13,7 +14,7 @@ function AppProvider({ children, ...options }) {
13
14
  react.useEffect(() => {
14
15
  if (connectingRef.current) return;
15
16
  connectingRef.current = true;
16
- chunkFB7GPBEM_cjs.connect(options).then(setApp, (err) => {
17
+ chunkHQLSVUOR_cjs.connect(options).then(setApp, (err) => {
17
18
  setError(err instanceof Error ? err : new Error(String(err)));
18
19
  });
19
20
  }, []);
@@ -78,12 +79,12 @@ function useCallTool(toolName) {
78
79
  const [data, setData] = react.useState(null);
79
80
  const callIdRef = react.useRef(0);
80
81
  const call = react.useCallback(
81
- async (args, options) => {
82
+ async (args) => {
82
83
  const id = ++callIdRef.current;
83
84
  setIsPending(true);
84
85
  setError(null);
85
86
  try {
86
- const result = await app.callTool(toolName, args, options);
87
+ const result = await app.callTool(toolName, args);
87
88
  if (id === callIdRef.current) {
88
89
  setData(result.data);
89
90
  setIsPending(false);
@@ -107,7 +108,7 @@ function useDataSync(callback) {
107
108
  const callbackRef = react.useRef(callback);
108
109
  callbackRef.current = callback;
109
110
  react.useEffect(() => {
110
- return app.on("data-changed", (event) => callbackRef.current(event));
111
+ return app.on(chunkISV6HFQE_cjs.RESOURCE_LIST_CHANGED_METHOD, (params) => callbackRef.current(params ?? {}));
111
112
  }, [app]);
112
113
  }
113
114
  var MODEL_CONTEXT_DEBOUNCE_MS = 250;
@@ -148,7 +149,7 @@ function useSendMessage() {
148
149
  function useAction() {
149
150
  const app = useAppContext();
150
151
  return react.useCallback(
151
- (name, params) => chunkFB7GPBEM_cjs.action(app, name, params),
152
+ (name, params) => chunkHQLSVUOR_cjs.action(app, name, params),
152
153
  [app]
153
154
  );
154
155
  }
@@ -159,7 +160,7 @@ function useFileUpload() {
159
160
  async (options) => {
160
161
  setIsPending(true);
161
162
  try {
162
- return await chunkFB7GPBEM_cjs.pickFile(app, options);
163
+ return await chunkHQLSVUOR_cjs.pickFile(app, options);
163
164
  } finally {
164
165
  setIsPending(false);
165
166
  }
@@ -170,7 +171,7 @@ function useFileUpload() {
170
171
  async (options) => {
171
172
  setIsPending(true);
172
173
  try {
173
- return await chunkFB7GPBEM_cjs.pickFiles(app, options);
174
+ return await chunkHQLSVUOR_cjs.pickFiles(app, options);
174
175
  } finally {
175
176
  setIsPending(false);
176
177
  }
@@ -255,7 +256,7 @@ function useCallToolAsTask(toolName) {
255
256
  refreshFailureCountRef.current = 0;
256
257
  let handle;
257
258
  try {
258
- handle = await chunkFB7GPBEM_cjs.callToolAsTask(app, toolName, args, options);
259
+ handle = await chunkHQLSVUOR_cjs.callToolAsTask(app, toolName, args, options);
259
260
  } catch (err) {
260
261
  if (gen !== genRef.current) throw err;
261
262
  const e = err instanceof Error ? err : new Error(String(err));