@nimblebrain/synapse 0.19.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.
- package/README.md +20 -25
- package/dist/check-JEM5PN64.cjs +435 -0
- package/dist/check-JEM5PN64.cjs.map +1 -0
- package/dist/check-YDZUQT6A.js +429 -0
- package/dist/check-YDZUQT6A.js.map +1 -0
- package/dist/{chunk-RU64F5RS.cjs → chunk-BNF352SA.cjs} +57 -146
- package/dist/chunk-BNF352SA.cjs.map +1 -0
- package/dist/{chunk-DRLVEHCD.js → chunk-DJS2FKBN.js} +21 -79
- package/dist/chunk-DJS2FKBN.js.map +1 -0
- package/dist/chunk-H3INUKA3.js +32 -0
- package/dist/chunk-H3INUKA3.js.map +1 -0
- package/dist/chunk-HQLSVUOR.cjs +667 -0
- package/dist/chunk-HQLSVUOR.cjs.map +1 -0
- package/dist/{chunk-NP7ROSH2.cjs → chunk-ISV6HFQE.cjs} +14 -7
- package/dist/chunk-ISV6HFQE.cjs.map +1 -0
- package/dist/chunk-TR2YNWZ5.js +658 -0
- package/dist/chunk-TR2YNWZ5.js.map +1 -0
- package/dist/{chunk-OYLZ5OCK.js → chunk-UKP4W6DI.js} +57 -146
- package/dist/chunk-UKP4W6DI.js.map +1 -0
- package/dist/{chunk-SZWHFTXV.cjs → chunk-YSL7KJNU.cjs} +21 -81
- package/dist/chunk-YSL7KJNU.cjs.map +1 -0
- package/dist/codegen/cli.cjs +41 -4
- package/dist/codegen/cli.cjs.map +1 -1
- package/dist/codegen/cli.js +41 -4
- package/dist/codegen/cli.js.map +1 -1
- package/dist/codegen/index.cjs +0 -1
- package/dist/codegen/index.d.cts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/codegen/index.js +0 -1
- package/dist/connect.iife.global.js +34 -34
- package/dist/{detect-DZMJsyvZ.d.cts → detect-DVGaL2bH.d.cts} +39 -33
- package/dist/{detect-DBwc8a21.d.ts → detect-DVGaL2bH.d.ts} +39 -33
- package/dist/host/index.cjs +17 -18
- package/dist/host/index.d.cts +11 -27
- package/dist/host/index.d.ts +11 -27
- package/dist/host/index.js +2 -3
- package/dist/index.cjs +30 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -26
- package/dist/index.d.ts +72 -26
- package/dist/index.js +8 -9
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +9 -10
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +36 -8
- package/dist/react/index.d.ts +36 -8
- package/dist/react/index.js +3 -4
- package/dist/react/index.js.map +1 -1
- package/dist/schema-reader-776246MJ.js +3 -0
- package/dist/{schema-reader-GM4A4L44.js.map → schema-reader-776246MJ.js.map} +1 -1
- package/dist/{schema-reader-MWBRQ2TQ.cjs → schema-reader-B6LTGBMJ.cjs} +2 -3
- package/dist/{schema-reader-MWBRQ2TQ.cjs.map → schema-reader-B6LTGBMJ.cjs.map} +1 -1
- package/dist/{server-LR6T6C44.cjs → server-BR5MNFOB.cjs} +13 -14
- package/dist/server-BR5MNFOB.cjs.map +1 -0
- package/dist/{server-UHPEE55X.js → server-OOEEWAEG.js} +13 -14
- package/dist/server-OOEEWAEG.js.map +1 -0
- package/dist/synapse-runtime.iife.global.js +34 -34
- package/dist/synapse-ui.iife.global.js +4 -4
- package/dist/type-generator-MZ4X4DE5.js +3 -0
- package/dist/{type-generator-ZYSVFK3X.js.map → type-generator-MZ4X4DE5.js.map} +1 -1
- package/dist/{type-generator-YTPFWLVG.cjs → type-generator-UA3L4OIA.cjs} +2 -3
- package/dist/{type-generator-YTPFWLVG.cjs.map → type-generator-UA3L4OIA.cjs.map} +1 -1
- package/dist/{types-CG08aO82.d.cts → types-BxPfGHKO.d.cts} +35 -43
- package/dist/{types-CG08aO82.d.ts → types-BxPfGHKO.d.ts} +35 -43
- package/dist/ui/base.cjs +0 -1
- package/dist/ui/base.cjs.map +1 -1
- package/dist/ui/base.js +0 -1
- package/dist/ui/base.js.map +1 -1
- package/dist/ui/index.cjs +0 -1
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.js +0 -1
- package/dist/ui/index.js.map +1 -1
- package/dist/vite/index.cjs +10 -10
- package/dist/vite/index.cjs.map +1 -1
- package/dist/vite/index.js +6 -6
- package/dist/vite/index.js.map +1 -1
- package/dist/{writer-HDRCQSX2.cjs → writer-NDK4U6C5.cjs} +3 -3
- package/dist/{writer-HDRCQSX2.cjs.map → writer-NDK4U6C5.cjs.map} +1 -1
- package/dist/{writer-QNDPMEYD.js → writer-ZNBXYUYC.js} +3 -3
- package/dist/{writer-QNDPMEYD.js.map → writer-ZNBXYUYC.js.map} +1 -1
- package/package.json +6 -3
- package/dist/chunk-54W6IAHM.js +0 -24
- package/dist/chunk-54W6IAHM.js.map +0 -1
- package/dist/chunk-DRLVEHCD.js.map +0 -1
- package/dist/chunk-NP7ROSH2.cjs.map +0 -1
- package/dist/chunk-OYLZ5OCK.js.map +0 -1
- package/dist/chunk-P5DBJFSQ.js +0 -8459
- package/dist/chunk-P5DBJFSQ.js.map +0 -1
- package/dist/chunk-PZ5AY32C.js +0 -9
- package/dist/chunk-PZ5AY32C.js.map +0 -1
- package/dist/chunk-Q7SFCCGT.cjs +0 -11
- package/dist/chunk-Q7SFCCGT.cjs.map +0 -1
- package/dist/chunk-QXDCA7QW.cjs +0 -8466
- package/dist/chunk-QXDCA7QW.cjs.map +0 -1
- package/dist/chunk-RU64F5RS.cjs.map +0 -1
- package/dist/chunk-SZWHFTXV.cjs.map +0 -1
- package/dist/schema-reader-GM4A4L44.js +0 -4
- package/dist/server-LR6T6C44.cjs.map +0 -1
- package/dist/server-UHPEE55X.js.map +0 -1
- package/dist/type-generator-ZYSVFK3X.js +0 -4
package/dist/index.d.cts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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-
|
|
3
|
-
export {
|
|
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
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, g as connectUI, h as detectHostKind } from './detect-
|
|
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';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Connect to an MCP Apps host.
|
|
@@ -20,18 +20,6 @@ export { C as ConnectUIOptions, a as HostCapabilities, b as HostKind, c as HostU
|
|
|
20
20
|
*/
|
|
21
21
|
declare function connect(options: ConnectOptions): Promise<App>;
|
|
22
22
|
|
|
23
|
-
/**
|
|
24
|
-
* Host-context key carrying `@font-face` descriptors.
|
|
25
|
-
*
|
|
26
|
-
* A NimbleBrain extension (`synapse/` prefix), because the ext-apps spec has no
|
|
27
|
-
* equivalent: `styles.variables` is a flat `Record<McpUiStyleVariableKey, string>`
|
|
28
|
-
* that can name a font family but cannot carry the rule that loads it.
|
|
29
|
-
* `McpUiHostContext` declares `[key: string]: unknown` for forward
|
|
30
|
-
* compatibility, so riding an extra key is spec-legal, and hosts that don't send
|
|
31
|
-
* it degrade to the web-safe token fallbacks.
|
|
32
|
-
*/
|
|
33
|
-
declare const FONT_FACES_CONTEXT_KEY = "synapse/fontFaces";
|
|
34
|
-
|
|
35
23
|
/**
|
|
36
24
|
* Hand the user a file to save, over the ext-apps `ui/download-file` request.
|
|
37
25
|
*
|
|
@@ -59,29 +47,86 @@ declare const FONT_FACES_CONTEXT_KEY = "synapse/fontFaces";
|
|
|
59
47
|
* embedded resource has no name field of its own.
|
|
60
48
|
*
|
|
61
49
|
* Resolves with the host's result: `{ isError: true }` when the host declined
|
|
62
|
-
* or the user cancelled. Rejects
|
|
63
|
-
*
|
|
64
|
-
* request may never answer it, and a request has no deadline.
|
|
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.
|
|
65
53
|
*/
|
|
66
54
|
declare function downloadFile(app: App, filename: string, content: string | Blob, mimeType?: string): Promise<McpUiDownloadFileResult>;
|
|
67
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.
|
|
109
|
+
*/
|
|
110
|
+
declare function hostSupports(app: App, extension: NimbleBrainExtension): boolean;
|
|
68
111
|
/**
|
|
69
112
|
* Trigger a host-side action.
|
|
70
113
|
*
|
|
71
|
-
* Sends a command *to* the host (navigate, open a panel).
|
|
72
|
-
*
|
|
114
|
+
* Sends a command *to* the host (navigate, open a panel). A no-op when the host
|
|
115
|
+
* did not declare `ai.nimblebrain/action`.
|
|
73
116
|
*/
|
|
74
117
|
declare function action(app: App, name: string, params?: Record<string, unknown>): void;
|
|
75
118
|
/**
|
|
76
119
|
* Request one file from the user via the host's native file picker.
|
|
77
120
|
*
|
|
78
|
-
* Resolves `null` if the user cancels.
|
|
121
|
+
* Resolves `null` if the user cancels. Rejects with `HostCapabilityError` when
|
|
122
|
+
* the host did not declare `ai.nimblebrain/request-file`.
|
|
79
123
|
*/
|
|
80
124
|
declare function pickFile(app: App, options?: RequestFileOptions): Promise<FileResult | null>;
|
|
81
125
|
/**
|
|
82
126
|
* Request several files from the user.
|
|
83
127
|
*
|
|
84
|
-
* Resolves `[]` if the user cancels.
|
|
128
|
+
* Resolves `[]` if the user cancels. Rejects with `HostCapabilityError` when
|
|
129
|
+
* the host did not declare `ai.nimblebrain/request-file`.
|
|
85
130
|
*/
|
|
86
131
|
declare function pickFiles(app: App, options?: RequestFileOptions): Promise<FileResult[]>;
|
|
87
132
|
|
|
@@ -99,10 +144,11 @@ declare function pickFiles(app: App, options?: RequestFileOptions): Promise<File
|
|
|
99
144
|
* const result = await handle.result();
|
|
100
145
|
* ```
|
|
101
146
|
*
|
|
102
|
-
*
|
|
103
|
-
* a requestor MUST NOT task-augment
|
|
104
|
-
*
|
|
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`.
|
|
105
151
|
*/
|
|
106
152
|
declare function callToolAsTask<TOutput = unknown>(app: App, toolName: string, args?: unknown, options?: CallToolAsTaskOptions): Promise<TaskHandle<TOutput>>;
|
|
107
153
|
|
|
108
|
-
export { App, CallToolAsTaskOptions, ConnectOptions,
|
|
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,9 +1,9 @@
|
|
|
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-
|
|
3
|
-
export {
|
|
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
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, g as connectUI, h as detectHostKind } from './detect-
|
|
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';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Connect to an MCP Apps host.
|
|
@@ -20,18 +20,6 @@ export { C as ConnectUIOptions, a as HostCapabilities, b as HostKind, c as HostU
|
|
|
20
20
|
*/
|
|
21
21
|
declare function connect(options: ConnectOptions): Promise<App>;
|
|
22
22
|
|
|
23
|
-
/**
|
|
24
|
-
* Host-context key carrying `@font-face` descriptors.
|
|
25
|
-
*
|
|
26
|
-
* A NimbleBrain extension (`synapse/` prefix), because the ext-apps spec has no
|
|
27
|
-
* equivalent: `styles.variables` is a flat `Record<McpUiStyleVariableKey, string>`
|
|
28
|
-
* that can name a font family but cannot carry the rule that loads it.
|
|
29
|
-
* `McpUiHostContext` declares `[key: string]: unknown` for forward
|
|
30
|
-
* compatibility, so riding an extra key is spec-legal, and hosts that don't send
|
|
31
|
-
* it degrade to the web-safe token fallbacks.
|
|
32
|
-
*/
|
|
33
|
-
declare const FONT_FACES_CONTEXT_KEY = "synapse/fontFaces";
|
|
34
|
-
|
|
35
23
|
/**
|
|
36
24
|
* Hand the user a file to save, over the ext-apps `ui/download-file` request.
|
|
37
25
|
*
|
|
@@ -59,29 +47,86 @@ declare const FONT_FACES_CONTEXT_KEY = "synapse/fontFaces";
|
|
|
59
47
|
* embedded resource has no name field of its own.
|
|
60
48
|
*
|
|
61
49
|
* Resolves with the host's result: `{ isError: true }` when the host declined
|
|
62
|
-
* or the user cancelled. Rejects
|
|
63
|
-
*
|
|
64
|
-
* request may never answer it, and a request has no deadline.
|
|
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.
|
|
65
53
|
*/
|
|
66
54
|
declare function downloadFile(app: App, filename: string, content: string | Blob, mimeType?: string): Promise<McpUiDownloadFileResult>;
|
|
67
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.
|
|
109
|
+
*/
|
|
110
|
+
declare function hostSupports(app: App, extension: NimbleBrainExtension): boolean;
|
|
68
111
|
/**
|
|
69
112
|
* Trigger a host-side action.
|
|
70
113
|
*
|
|
71
|
-
* Sends a command *to* the host (navigate, open a panel).
|
|
72
|
-
*
|
|
114
|
+
* Sends a command *to* the host (navigate, open a panel). A no-op when the host
|
|
115
|
+
* did not declare `ai.nimblebrain/action`.
|
|
73
116
|
*/
|
|
74
117
|
declare function action(app: App, name: string, params?: Record<string, unknown>): void;
|
|
75
118
|
/**
|
|
76
119
|
* Request one file from the user via the host's native file picker.
|
|
77
120
|
*
|
|
78
|
-
* Resolves `null` if the user cancels.
|
|
121
|
+
* Resolves `null` if the user cancels. Rejects with `HostCapabilityError` when
|
|
122
|
+
* the host did not declare `ai.nimblebrain/request-file`.
|
|
79
123
|
*/
|
|
80
124
|
declare function pickFile(app: App, options?: RequestFileOptions): Promise<FileResult | null>;
|
|
81
125
|
/**
|
|
82
126
|
* Request several files from the user.
|
|
83
127
|
*
|
|
84
|
-
* Resolves `[]` if the user cancels.
|
|
128
|
+
* Resolves `[]` if the user cancels. Rejects with `HostCapabilityError` when
|
|
129
|
+
* the host did not declare `ai.nimblebrain/request-file`.
|
|
85
130
|
*/
|
|
86
131
|
declare function pickFiles(app: App, options?: RequestFileOptions): Promise<FileResult[]>;
|
|
87
132
|
|
|
@@ -99,10 +144,11 @@ declare function pickFiles(app: App, options?: RequestFileOptions): Promise<File
|
|
|
99
144
|
* const result = await handle.result();
|
|
100
145
|
* ```
|
|
101
146
|
*
|
|
102
|
-
*
|
|
103
|
-
* a requestor MUST NOT task-augment
|
|
104
|
-
*
|
|
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`.
|
|
105
151
|
*/
|
|
106
152
|
declare function callToolAsTask<TOutput = unknown>(app: App, toolName: string, args?: unknown, options?: CallToolAsTaskOptions): Promise<TaskHandle<TOutput>>;
|
|
107
153
|
|
|
108
|
-
export { App, CallToolAsTaskOptions, ConnectOptions,
|
|
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,17 +1,16 @@
|
|
|
1
|
-
export { HostUnsupportedError, SYNAPSE_DATA_ELEMENT_ID, connectUI, detectHostKind } from './chunk-
|
|
2
|
-
import { internalsFor } from './chunk-
|
|
3
|
-
export { action, callToolAsTask, connect, pickFile, pickFiles } from './chunk-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import './chunk-PZ5AY32C.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';
|
|
7
6
|
import { DOWNLOAD_FILE_METHOD } from '@modelcontextprotocol/ext-apps';
|
|
8
7
|
|
|
9
8
|
var BASE64_CHUNK = 32768;
|
|
10
9
|
async function downloadFile(app, filename, content, mimeType) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
throw new Error("downloadFile is not supported in this host");
|
|
10
|
+
if (!app.hostCapabilities.downloadFile) {
|
|
11
|
+
throw new HostCapabilityError("downloadFile", "downloadFile");
|
|
14
12
|
}
|
|
13
|
+
const internals = internalsFor(app);
|
|
15
14
|
const resolvedMime = mimeType || (content instanceof Blob ? content.type : "") || "application/octet-stream";
|
|
16
15
|
const uri = `file:///${filename}`;
|
|
17
16
|
const resource = typeof content === "string" ? { uri, mimeType: resolvedMime, text: content } : { uri, mimeType: resolvedMime, blob: await toBase64(content) };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/download-file.ts"],"names":[],"mappings":"
|
|
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"]}
|
package/dist/react/index.cjs
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
require('../chunk-
|
|
5
|
-
var
|
|
6
|
-
require('../chunk-Q7SFCCGT.cjs');
|
|
3
|
+
var chunkHQLSVUOR_cjs = require('../chunk-HQLSVUOR.cjs');
|
|
4
|
+
require('../chunk-YSL7KJNU.cjs');
|
|
5
|
+
var chunkISV6HFQE_cjs = require('../chunk-ISV6HFQE.cjs');
|
|
7
6
|
var react = require('react');
|
|
8
7
|
var jsxRuntime = require('react/jsx-runtime');
|
|
9
8
|
|
|
@@ -15,7 +14,7 @@ function AppProvider({ children, ...options }) {
|
|
|
15
14
|
react.useEffect(() => {
|
|
16
15
|
if (connectingRef.current) return;
|
|
17
16
|
connectingRef.current = true;
|
|
18
|
-
|
|
17
|
+
chunkHQLSVUOR_cjs.connect(options).then(setApp, (err) => {
|
|
19
18
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
20
19
|
});
|
|
21
20
|
}, []);
|
|
@@ -109,7 +108,7 @@ function useDataSync(callback) {
|
|
|
109
108
|
const callbackRef = react.useRef(callback);
|
|
110
109
|
callbackRef.current = callback;
|
|
111
110
|
react.useEffect(() => {
|
|
112
|
-
return app.on(
|
|
111
|
+
return app.on(chunkISV6HFQE_cjs.RESOURCE_LIST_CHANGED_METHOD, (params) => callbackRef.current(params ?? {}));
|
|
113
112
|
}, [app]);
|
|
114
113
|
}
|
|
115
114
|
var MODEL_CONTEXT_DEBOUNCE_MS = 250;
|
|
@@ -150,7 +149,7 @@ function useSendMessage() {
|
|
|
150
149
|
function useAction() {
|
|
151
150
|
const app = useAppContext();
|
|
152
151
|
return react.useCallback(
|
|
153
|
-
(name, params) =>
|
|
152
|
+
(name, params) => chunkHQLSVUOR_cjs.action(app, name, params),
|
|
154
153
|
[app]
|
|
155
154
|
);
|
|
156
155
|
}
|
|
@@ -161,7 +160,7 @@ function useFileUpload() {
|
|
|
161
160
|
async (options) => {
|
|
162
161
|
setIsPending(true);
|
|
163
162
|
try {
|
|
164
|
-
return await
|
|
163
|
+
return await chunkHQLSVUOR_cjs.pickFile(app, options);
|
|
165
164
|
} finally {
|
|
166
165
|
setIsPending(false);
|
|
167
166
|
}
|
|
@@ -172,7 +171,7 @@ function useFileUpload() {
|
|
|
172
171
|
async (options) => {
|
|
173
172
|
setIsPending(true);
|
|
174
173
|
try {
|
|
175
|
-
return await
|
|
174
|
+
return await chunkHQLSVUOR_cjs.pickFiles(app, options);
|
|
176
175
|
} finally {
|
|
177
176
|
setIsPending(false);
|
|
178
177
|
}
|
|
@@ -257,7 +256,7 @@ function useCallToolAsTask(toolName) {
|
|
|
257
256
|
refreshFailureCountRef.current = 0;
|
|
258
257
|
let handle;
|
|
259
258
|
try {
|
|
260
|
-
handle = await
|
|
259
|
+
handle = await chunkHQLSVUOR_cjs.callToolAsTask(app, toolName, args, options);
|
|
261
260
|
} catch (err) {
|
|
262
261
|
if (gen !== genRef.current) throw err;
|
|
263
262
|
const e = err instanceof Error ? err : new Error(String(err));
|
package/dist/react/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/app-provider.tsx","../../src/react/hooks.ts"],"names":["createContext","useState","useRef","useEffect","connect","useContext","useCallback","RESOURCE_LIST_CHANGED_METHOD","action","pickFile","pickFiles","isTerminal","callToolAsTask"],"mappings":"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","file":"index.cjs","sourcesContent":["import { createContext, type ReactNode, useContext, useEffect, useRef, useState } from \"react\";\nimport { connect } from \"../connect.js\";\nimport type { App, ConnectOptions } from \"../types.js\";\n\nconst AppContext = createContext<App | null>(null);\n\nexport interface AppProviderProps extends ConnectOptions {\n children: ReactNode;\n}\n\n/**\n * Connect on mount and provide the {@link App} to everything below.\n *\n * Renders nothing until the handshake completes, so no hook below can observe\n * a half-connected app and no tool call can be fired into a host that has not\n * answered `ui/initialize` yet.\n */\nexport function AppProvider({ children, ...options }: AppProviderProps) {\n const [app, setApp] = useState<App | null>(null);\n const [error, setError] = useState<Error | null>(null);\n const connectingRef = useRef(false);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: connect once on mount\n useEffect(() => {\n // A ref, not cleanup-on-unmount: StrictMode unmounts and immediately\n // remounts, and the transport must survive that. The app is GC'd when the\n // provider is truly gone.\n if (connectingRef.current) return;\n connectingRef.current = true;\n\n connect(options).then(setApp, (err: unknown) => {\n setError(err instanceof Error ? err : new Error(String(err)));\n });\n }, []);\n\n // A host that answers `ui/initialize` with an error is a failure the app\n // cannot proceed through, and rendering nothing forever is the worst way to\n // report it: a blank pane and an unhandled rejection in a console nobody has\n // open. Re-throwing during render makes it an ordinary React error, so it\n // reaches the nearest error boundary and is loud in development.\n if (error) throw error;\n\n // Still connecting. Nothing below can observe a half-connected app, and no\n // hook can fire a call into a host that has not answered yet.\n if (!app) return null;\n\n return <AppContext.Provider value={app}>{children}</AppContext.Provider>;\n}\n\nexport function useAppContext(): App {\n const ctx = useContext(AppContext);\n if (!ctx) {\n throw new Error(\n \"useApp must be used within an <AppProvider>. Wrap your component tree with <AppProvider>.\",\n );\n }\n return ctx;\n}\n","import type { McpUiHostContext } from \"@modelcontextprotocol/ext-apps\";\nimport type {\n ResourceListChangedNotification,\n Task,\n TaskStatus,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { RESOURCE_LIST_CHANGED_METHOD } from \"../event-map.js\";\nimport { pickFile, pickFiles, action as sendAction } from \"../extensions.js\";\nimport { callToolAsTask } from \"../task-handle.js\";\nimport type {\n App,\n CallToolAsTaskOptions,\n FileResult,\n ModelContext,\n RequestFileOptions,\n TaskHandle,\n Theme,\n ToolCallResult,\n ToolResultData,\n} from \"../types.js\";\nimport { useAppContext } from \"./app-provider.js\";\n\n/** The connected app. Throws outside an `<AppProvider>`. */\nexport function useApp(): App {\n return useAppContext();\n}\n\n// -----------------------------------------------------------------------------\n// Host state\n// -----------------------------------------------------------------------------\n\n/**\n * The current theme, re-rendering only when it actually moves.\n *\n * `App` filters `host-context-changed` notifications through a theme equality\n * check, so a context update that leaves the derived theme untouched (a\n * workspace switch, say) does not re-render every themed component.\n */\nexport function useTheme(): Theme {\n const app = useAppContext();\n const [theme, setTheme] = useState<Theme>(() => app.theme);\n\n useEffect(() => {\n // Sync in case the theme changed between render and effect.\n setTheme(app.theme);\n return app.on(\"theme-changed\", setTheme);\n }, [app]);\n\n return theme;\n}\n\n/**\n * The full ext-apps host context — spec fields (`theme`, `styles`,\n * `displayMode`, `toolInfo`) plus whatever the host publishes alongside.\n * Re-renders on every `host-context-changed`.\n *\n * Prefer `useTheme()` when only theming matters: it filters no-op fires.\n * Reach for this one for host extensions, e.g. on NimbleBrain:\n *\n * ```tsx\n * const { workspace } = useHostContext<{ workspace?: { id: string } }>();\n * ```\n */\nexport function useHostContext<T extends McpUiHostContext = McpUiHostContext>(): T {\n const app = useAppContext();\n const [ctx, setCtx] = useState<T>(() => app.hostContext as T);\n\n useEffect(() => {\n setCtx(app.hostContext as T);\n return app.on(\"host-context-changed\", (c) => setCtx(c as T));\n }, [app]);\n\n return ctx;\n}\n\n/** The latest tool result pushed by the host, or `null` before one arrives. */\nexport function useToolResult(): ToolResultData | null {\n const app = useAppContext();\n const [data, setData] = useState<ToolResultData | null>(null);\n\n useEffect(() => {\n return app.on(\"tool-result\", setData);\n }, [app]);\n\n return data;\n}\n\n/** The arguments the host is calling the bound tool with, as they arrive. */\nexport function useToolInput(): Record<string, unknown> | null {\n const app = useAppContext();\n const [input, setInput] = useState<Record<string, unknown> | null>(null);\n\n useEffect(() => {\n return app.on(\"tool-input\", setInput);\n }, [app]);\n\n return input;\n}\n\n/** Ask the host to resize this app's frame. */\nexport function useResize(): (width?: number, height?: number) => void {\n const app = useAppContext();\n return useCallback((width?: number, height?: number) => app.resize(width, height), [app]);\n}\n\n// -----------------------------------------------------------------------------\n// Tools\n// -----------------------------------------------------------------------------\n\nexport interface UseCallToolResult<TOutput> {\n call: (args?: Record<string, unknown>) => Promise<ToolCallResult<TOutput>>;\n isPending: boolean;\n error: Error | null;\n data: TOutput | null;\n}\n\n/** Call one tool, with pending/error/data state for the latest call. */\nexport function useCallTool<TOutput = unknown>(toolName: string): UseCallToolResult<TOutput> {\n const app = useAppContext();\n const [isPending, setIsPending] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [data, setData] = useState<TOutput | null>(null);\n const callIdRef = useRef(0);\n\n const call = useCallback(\n async (args?: Record<string, unknown>): Promise<ToolCallResult<TOutput>> => {\n const id = ++callIdRef.current;\n setIsPending(true);\n setError(null);\n\n try {\n const result = await app.callTool<TOutput>(toolName, args);\n // Stale guard: only update if this is still the latest call.\n if (id === callIdRef.current) {\n setData(result.data);\n setIsPending(false);\n }\n return result;\n } catch (err) {\n if (id === callIdRef.current) {\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n setIsPending(false);\n }\n throw err;\n }\n },\n [app, toolName],\n );\n\n return { call, isPending, error, data };\n}\n\n/**\n * Run `callback` when the app's own MCP server announces that its data changed.\n *\n * A server sends `notifications/resources/list_changed` from the write that\n * changed its data, and an MCP Apps host forwards it to that server's views\n * (host capability `serverResources.listChanged`). The callback receives the\n * notification's params as the spec defines them, or `{}` when the host sends\n * none. They name no server and no tool: the notification only ever comes from\n * this app's own server, and a change need not come from a tool call at all.\n */\nexport function useDataSync(\n callback: (params: NonNullable<ResourceListChangedNotification[\"params\"]>) => void,\n): void {\n const app = useAppContext();\n const callbackRef = useRef(callback);\n callbackRef.current = callback;\n\n useEffect(() => {\n return app.on(RESOURCE_LIST_CHANGED_METHOD, (params) => callbackRef.current(params ?? {}));\n }, [app]);\n}\n\n// -----------------------------------------------------------------------------\n// Agent-facing state\n// -----------------------------------------------------------------------------\n\n/** Debounce window for `useModelContext`, in milliseconds. */\nconst MODEL_CONTEXT_DEBOUNCE_MS = 250;\n\n/**\n * Push what the user is looking at to the agent (ext-apps\n * `ui/update-model-context`), debounced so a selection the user drags through\n * costs one frame rather than thirty.\n *\n * **Declarative** — pushes when `deps` change:\n * ```tsx\n * useModelContext(() => ({\n * state: { board: selectedBoard },\n * summary: `Viewing \"${selectedBoard?.name}\"`,\n * }), [selectedBoard]);\n * ```\n *\n * **Imperative** — returns a push function:\n * ```tsx\n * const push = useModelContext();\n * push({ board: selectedBoard }, \"Viewing board X\");\n * ```\n *\n * The debounce lives here rather than on `app.updateModelContext`, which\n * sends immediately: the rapid-change problem is a React one, and the plain\n * method should do what it says.\n */\nexport function useModelContext(): (state: Record<string, unknown>, summary?: string) => void;\nexport function useModelContext(factory: () => ModelContext, deps: unknown[]): void;\nexport function useModelContext(\n factory?: () => ModelContext,\n deps?: unknown[],\n): ((state: Record<string, unknown>, summary?: string) => void) | undefined {\n const app = useAppContext();\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const push = useCallback(\n (state: Record<string, unknown>, summary?: string) => {\n if (timerRef.current) clearTimeout(timerRef.current);\n timerRef.current = setTimeout(() => {\n timerRef.current = null;\n app.updateModelContext(state, summary);\n }, MODEL_CONTEXT_DEBOUNCE_MS);\n },\n [app],\n );\n\n useEffect(() => {\n return () => {\n if (timerRef.current) clearTimeout(timerRef.current);\n };\n }, []);\n\n // Declarative mode: push when deps change. The deps array is\n // caller-provided (mirrors useMemo/useEffect).\n const factoryRef = useRef(factory);\n factoryRef.current = factory;\n useEffect(() => {\n if (!factoryRef.current) return;\n const { state, summary } = factoryRef.current();\n push(state, summary);\n }, [...(deps ?? []), push]);\n\n if (!factory) return push;\n}\n\n/** Send a user message into the agent conversation. */\nexport function useSendMessage(): (\n text: string,\n context?: { action?: string; entity?: string },\n) => void {\n const app = useAppContext();\n return useCallback(\n (text: string, context?: { action?: string; entity?: string }) =>\n app.sendMessage(text, context),\n [app],\n );\n}\n\n// -----------------------------------------------------------------------------\n// NimbleBrain host extensions\n// -----------------------------------------------------------------------------\n\n/** Trigger a host-side action. No-op off a NimbleBrain host. */\nexport function useAction(): (name: string, params?: Record<string, unknown>) => void {\n const app = useAppContext();\n return useCallback(\n (name: string, params?: Record<string, unknown>) => sendAction(app, name, params),\n [app],\n );\n}\n\nexport interface UseFileUploadResult {\n pickFile: (options?: RequestFileOptions) => Promise<FileResult | null>;\n pickFiles: (options?: RequestFileOptions) => Promise<FileResult[]>;\n isPending: boolean;\n}\n\n/** The host's native file picker, with a pending flag. NimbleBrain only. */\nexport function useFileUpload(): UseFileUploadResult {\n const app = useAppContext();\n const [isPending, setIsPending] = useState(false);\n\n const one = useCallback(\n async (options?: RequestFileOptions) => {\n setIsPending(true);\n try {\n return await pickFile(app, options);\n } finally {\n setIsPending(false);\n }\n },\n [app],\n );\n\n const many = useCallback(\n async (options?: RequestFileOptions) => {\n setIsPending(true);\n try {\n return await pickFiles(app, options);\n } finally {\n setIsPending(false);\n }\n },\n [app],\n );\n\n return { pickFile: one, pickFiles: many, isPending };\n}\n\n// -----------------------------------------------------------------------------\n// useCallToolAsTask — lifecycle wrapper around `callToolAsTask(app, …)`\n// -----------------------------------------------------------------------------\n\n/**\n * Spec terminal status values for the MCP 2025-11-25 tasks utility.\n *\n * Typed via `satisfies TaskStatus` so a rename of any member of the\n * spec enum (`completed`/`failed`/`cancelled`) trips `tsc` — we never\n * hand-type these as bare string literals in comparisons.\n */\nconst COMPLETED_STATUS = \"completed\" satisfies TaskStatus;\nconst FAILED_STATUS = \"failed\" satisfies TaskStatus;\nconst CANCELLED_STATUS = \"cancelled\" satisfies TaskStatus;\n\nconst TERMINAL_STATUSES: ReadonlySet<TaskStatus> = new Set<TaskStatus>([\n COMPLETED_STATUS,\n FAILED_STATUS,\n CANCELLED_STATUS,\n]);\n\n/**\n * Fallback poll cadence used when the receiver's `CreateTaskResult.task`\n * carries no `pollInterval`. The effective fire delay is this value × 1.5\n * ≈ 7.5s, well below default TTLs but long enough to avoid hammering hosts\n * that do emit `notifications/tasks/status`.\n */\nconst DEFAULT_POLL_INTERVAL_MS = 5_000;\nconst POLL_FALLBACK_MULTIPLIER = 1.5;\n\n/**\n * Stop the poll fallback after this many consecutive `refresh()` failures.\n * Bridge teardown / TTL eviction / network outage all manifest as repeated\n * `tasks/get` rejections. Without a guard the timer re-arms forever; with\n * it we stop polling silently after `MAX_REFRESH_FAILURES` strikes — the\n * blocking `result()` path remains the authoritative source of truth, so\n * giving up on polling never loses the terminal value.\n */\nconst MAX_REFRESH_FAILURES = 5;\n\nexport interface UseCallToolAsTaskResult<TInput, TOutput> {\n /**\n * Start (or re-start) a task-augmented tool call. Returns the\n * resolved `TaskHandle` so callers can `await fire(...)` if they\n * want to know when the server has accepted the task, but reading\n * `task`/`result`/`error` from the hook is usually enough.\n *\n * Re-firing while a previous task is still in flight detaches this\n * hook from the prior handle (stops polling, unsubscribes) but does\n * NOT cancel the server-side task — the task keeps running and its\n * result may still be fetched elsewhere (e.g. on page revisit).\n */\n fire(args?: TInput, options?: CallToolAsTaskOptions): Promise<TaskHandle<TOutput>>;\n /** Latest `Task` state, or `null` before `fire()` has been called. */\n task: Task | null;\n /** Populated once `handle.result()` resolves non-error. */\n result: ToolCallResult<TOutput> | null;\n /** Populated on rejection or when `result.isError === true`. */\n error: Error | null;\n /** `true` while the task is non-terminal (`working` / `input_required`). */\n isWorking: boolean;\n /** `true` when `task.status ∈ {completed, failed, cancelled}`. */\n isTerminal: boolean;\n /**\n * Cancel the active task via `tasks/cancel`. No-op when no task is\n * active. Swallowed errors surface via `error`.\n */\n cancel(): Promise<void>;\n}\n\n/**\n * React wrapper around `callToolAsTask(app, …)`.\n *\n * Handles the full MCP 2025-11-25 task lifecycle:\n *\n * 1. `fire(args, options?)` sends the task-augmented `tools/call` and\n * stores the returned `TaskHandle` in a ref.\n * 2. Subscribes to `handle.onStatus` — updates `task` whenever the\n * host emits `notifications/tasks/status` (OPTIONAL per spec).\n * 3. Starts a polling fallback: if no status notification arrives\n * within `pollInterval × 1.5` (defaulting to ~7.5s), calls\n * `handle.refresh()` for canonical state. Stops on terminal.\n * 4. Awaits `handle.result()` in the background — resolves to either\n * `result` (success / `isError: false`) or `error` (network reject\n * OR `result.isError === true`).\n *\n * Cleanup (unmount or re-fire) unsubscribes from status events and\n * clears the poll timer, but does NOT cancel the server-side task —\n * the caller may remount and recover state by firing again, and tasks\n * outlive iframe teardown until TTL elapses.\n */\nexport function useCallToolAsTask<TInput = Record<string, unknown>, TOutput = unknown>(\n toolName: string,\n): UseCallToolAsTaskResult<TInput, TOutput> {\n const app = useAppContext();\n\n const [task, setTask] = useState<Task | null>(null);\n const [result, setResult] = useState<ToolCallResult<TOutput> | null>(null);\n const [error, setError] = useState<Error | null>(null);\n\n // Per-fire generation counter. Every `fire()` increments; any\n // asynchronous callback (status listener, poll timer, `result()`\n // resolution) captures the gen at schedule time and bails if the\n // current gen has moved past it. This is the single source of truth\n // for \"is this work still relevant?\" — more robust than comparing\n // TaskHandle identity because handles can be detached by re-fire.\n const genRef = useRef(0);\n\n // Active handle + its teardown handles. We keep both in refs so the\n // hook's stable `fire`/`cancel` callbacks can reach the current\n // lifecycle state without re-binding on every render.\n const handleRef = useRef<TaskHandle<TOutput> | null>(null);\n const unsubscribeRef = useRef<(() => void) | null>(null);\n const pollTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n // `pollInterval × 1.5`, captured per-fire. Falls back to the 5s\n // default when the host didn't provide a `pollInterval` in the\n // initial CreateTaskResult.task.\n const pollDelayRef = useRef<number>(DEFAULT_POLL_INTERVAL_MS * POLL_FALLBACK_MULTIPLIER);\n\n // Track the latest known status out-of-band so the poll callback\n // can decide whether to keep polling without depending on the\n // `task` React state (which lags a render behind setState).\n const terminalRef = useRef<boolean>(false);\n\n // Consecutive `refresh()` failure count for the active fire. Reset\n // on every successful refresh, status notification, or new fire.\n const refreshFailureCountRef = useRef<number>(0);\n\n const clearPollTimer = useCallback(() => {\n if (pollTimerRef.current !== null) {\n clearTimeout(pollTimerRef.current);\n pollTimerRef.current = null;\n }\n }, []);\n\n const detachCurrent = useCallback(() => {\n clearPollTimer();\n if (unsubscribeRef.current) {\n unsubscribeRef.current();\n unsubscribeRef.current = null;\n }\n handleRef.current = null;\n }, [clearPollTimer]);\n\n const scheduleNextPoll = useCallback(\n (gen: number) => {\n clearPollTimer();\n if (terminalRef.current) return;\n pollTimerRef.current = setTimeout(() => {\n // Bail if this fire has been superseded or torn down.\n if (gen !== genRef.current) return;\n const h = handleRef.current;\n if (!h) return;\n if (terminalRef.current) return;\n // `refresh()` is the canonical source for `createdAt` /\n // `lastUpdatedAt` / `ttl` — notification-derived Tasks carry\n // placeholders per the router in `task-handle.ts`.\n h.refresh().then(\n (fresh) => {\n if (gen !== genRef.current) return;\n refreshFailureCountRef.current = 0;\n setTask(fresh);\n const isTerminal = TERMINAL_STATUSES.has(fresh.status);\n terminalRef.current = isTerminal;\n if (!isTerminal) scheduleNextPoll(gen);\n },\n () => {\n // Swallow refresh errors — the blocking `tasks/result` is\n // the authoritative path; polling is best-effort. But guard\n // against runaway re-arming: if the bridge is gone or the\n // task TTL has elapsed, every refresh rejects. Stop after\n // MAX_REFRESH_FAILURES consecutive strikes; result() will\n // still surface a terminal value or error when it settles.\n if (gen !== genRef.current) return;\n refreshFailureCountRef.current += 1;\n if (refreshFailureCountRef.current >= MAX_REFRESH_FAILURES) return;\n if (!terminalRef.current) scheduleNextPoll(gen);\n },\n );\n }, pollDelayRef.current);\n },\n [clearPollTimer],\n );\n\n const fire = useCallback(\n async (args?: TInput, options?: CallToolAsTaskOptions): Promise<TaskHandle<TOutput>> => {\n // Detach any in-flight prior task BEFORE incrementing the gen so\n // its callbacks see the new gen and bail. (Incrementing then\n // detaching would also work, but detach-first makes the order\n // obvious: stop listening, bump generation, start fresh.)\n detachCurrent();\n const gen = ++genRef.current;\n\n // Reset per-fire state. Don't wipe `task` yet — `callToolAsTask`\n // is async; showing the previous terminal state briefly is less\n // jarring than flicker to null → working. We clear on resolution.\n setResult(null);\n setError(null);\n terminalRef.current = false;\n refreshFailureCountRef.current = 0;\n\n let handle: TaskHandle<TOutput>;\n try {\n handle = await callToolAsTask<TOutput>(app, toolName, args, options);\n } catch (err) {\n if (gen !== genRef.current) throw err;\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n throw err;\n }\n\n // Caller superseded the fire between request and response —\n // don't attach listeners, but still return the handle so the\n // awaiter can observe it.\n if (gen !== genRef.current) return handle;\n\n handleRef.current = handle;\n\n // Derive the fallback poll delay from the receiver's advertised\n // `pollInterval`. Spec allows it to be absent; we then use the\n // 5s default.\n const hintedInterval = handle.task.pollInterval;\n pollDelayRef.current =\n typeof hintedInterval === \"number\" && hintedInterval > 0\n ? hintedInterval * POLL_FALLBACK_MULTIPLIER\n : DEFAULT_POLL_INTERVAL_MS * POLL_FALLBACK_MULTIPLIER;\n\n setTask(handle.task);\n terminalRef.current = TERMINAL_STATUSES.has(handle.task.status);\n\n // Subscribe to `notifications/tasks/status`. Each notification\n // resets the poll countdown (that's the whole point of the\n // \"notification OR polling\" contract — if notifications flow,\n // we don't poll; if they don't, the timer fires).\n unsubscribeRef.current = handle.onStatus((updated) => {\n if (gen !== genRef.current) return;\n refreshFailureCountRef.current = 0;\n setTask(updated);\n const isTerminal = TERMINAL_STATUSES.has(updated.status);\n terminalRef.current = isTerminal;\n if (isTerminal) {\n clearPollTimer();\n } else {\n scheduleNextPoll(gen);\n }\n });\n\n // Kick off the blocking result fetch — this is the authoritative\n // terminal value regardless of whether notifications or polls\n // landed in between. By spec, `tasks/result` blocks until the task\n // reaches a terminal status, so when this settles we KNOW the task\n // is terminal — stop polling and synthesize a terminal `task`\n // status so derived flags (`isTerminal`, `isWorking`) match the\n // populated `result` / `error` immediately.\n handle.result().then(\n (res) => {\n if (gen !== genRef.current) return;\n terminalRef.current = true;\n clearPollTimer();\n // Synthesize the terminal Task: failed if `isError`, otherwise\n // completed. The next status notification or refresh would\n // confirm this, but we want internal state consistent the\n // instant `result` is populated — a \"result populated while\n // isWorking=true\" render is incoherent for consumers.\n setTask((prev) => {\n const status: TaskStatus = res.isError ? FAILED_STATUS : COMPLETED_STATUS;\n const now = new Date().toISOString();\n return prev\n ? { ...prev, status, lastUpdatedAt: now }\n : {\n taskId: handle.task.taskId,\n status,\n ttl: handle.task.ttl,\n createdAt: handle.task.createdAt,\n lastUpdatedAt: now,\n };\n });\n if (res.isError) {\n // Spec: `CallToolResult.isError === true` is a tool-level\n // error, not a protocol error. Surface via `error` for\n // consumers who treat it as a failure, but also populate\n // `result` so callers inspecting the raw content block\n // still have access.\n setResult(res);\n const msg =\n typeof res.data === \"string\" && res.data.length > 0\n ? res.data\n : `Tool \"${toolName}\" returned isError: true`;\n setError(new Error(msg));\n } else {\n setResult(res);\n }\n },\n (err) => {\n if (gen !== genRef.current) return;\n // result() rejection means the `tasks/result` RPC failed\n // (transport error, taskId not found, bridge teardown). We\n // can't know the server-side task's actual final state, but\n // we know polling won't recover here either — same transport.\n // Mark terminal and synthesize `failed` status for UX\n // coherence; the populated `error` tells the consumer what\n // specifically went wrong.\n terminalRef.current = true;\n clearPollTimer();\n setTask((prev) => {\n const now = new Date().toISOString();\n return prev\n ? { ...prev, status: FAILED_STATUS, lastUpdatedAt: now }\n : {\n taskId: handle.task.taskId,\n status: FAILED_STATUS,\n ttl: handle.task.ttl,\n createdAt: handle.task.createdAt,\n lastUpdatedAt: now,\n };\n });\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n },\n );\n\n // Start the poll fallback only if we aren't already terminal.\n if (!terminalRef.current) scheduleNextPoll(gen);\n\n return handle;\n },\n [app, toolName, detachCurrent, clearPollTimer, scheduleNextPoll],\n );\n\n const cancel = useCallback(async (): Promise<void> => {\n const h = handleRef.current;\n if (!h) return;\n const gen = genRef.current;\n try {\n const cancelled = await h.cancel();\n if (gen !== genRef.current) return;\n setTask(cancelled);\n terminalRef.current = TERMINAL_STATUSES.has(cancelled.status);\n clearPollTimer();\n } catch (err) {\n if (gen !== genRef.current) return;\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n }\n }, [clearPollTimer]);\n\n // Cleanup on unmount: stop polling, drop the status subscription.\n // Deliberately do NOT call `handle.cancel()` — the server-side task\n // keeps running so a remount can recover state.\n useEffect(() => {\n return () => {\n genRef.current += 1;\n if (pollTimerRef.current !== null) {\n clearTimeout(pollTimerRef.current);\n pollTimerRef.current = null;\n }\n if (unsubscribeRef.current) {\n unsubscribeRef.current();\n unsubscribeRef.current = null;\n }\n handleRef.current = null;\n };\n }, []);\n\n const isTerminal = task !== null && TERMINAL_STATUSES.has(task.status);\n const isWorking = task !== null && !isTerminal;\n\n return { fire, task, result, error, isWorking, isTerminal, cancel };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/react/app-provider.tsx","../../src/react/hooks.ts"],"names":["createContext","useState","useRef","useEffect","connect","useContext","useCallback","RESOURCE_LIST_CHANGED_METHOD","action","pickFile","pickFiles","isTerminal","callToolAsTask"],"mappings":"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","file":"index.cjs","sourcesContent":["import { createContext, type ReactNode, useContext, useEffect, useRef, useState } from \"react\";\nimport { connect } from \"../connect.js\";\nimport type { App, ConnectOptions } from \"../types.js\";\n\nconst AppContext = createContext<App | null>(null);\n\nexport interface AppProviderProps extends ConnectOptions {\n children: ReactNode;\n}\n\n/**\n * Connect on mount and provide the {@link App} to everything below.\n *\n * Renders nothing until the handshake completes, so no hook below can observe\n * a half-connected app and no tool call can be fired into a host that has not\n * answered `ui/initialize` yet.\n */\nexport function AppProvider({ children, ...options }: AppProviderProps) {\n const [app, setApp] = useState<App | null>(null);\n const [error, setError] = useState<Error | null>(null);\n const connectingRef = useRef(false);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: connect once on mount\n useEffect(() => {\n // A ref, not cleanup-on-unmount: StrictMode unmounts and immediately\n // remounts, and the transport must survive that. The app is GC'd when the\n // provider is truly gone.\n if (connectingRef.current) return;\n connectingRef.current = true;\n\n connect(options).then(setApp, (err: unknown) => {\n setError(err instanceof Error ? err : new Error(String(err)));\n });\n }, []);\n\n // A host that answers `ui/initialize` with an error is a failure the app\n // cannot proceed through, and rendering nothing forever is the worst way to\n // report it: a blank pane and an unhandled rejection in a console nobody has\n // open. Re-throwing during render makes it an ordinary React error, so it\n // reaches the nearest error boundary and is loud in development.\n if (error) throw error;\n\n // Still connecting. Nothing below can observe a half-connected app, and no\n // hook can fire a call into a host that has not answered yet.\n if (!app) return null;\n\n return <AppContext.Provider value={app}>{children}</AppContext.Provider>;\n}\n\nexport function useAppContext(): App {\n const ctx = useContext(AppContext);\n if (!ctx) {\n throw new Error(\n \"useApp must be used within an <AppProvider>. Wrap your component tree with <AppProvider>.\",\n );\n }\n return ctx;\n}\n","import type { McpUiHostContext } from \"@modelcontextprotocol/ext-apps\";\nimport type {\n ResourceListChangedNotification,\n Task,\n TaskStatus,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { RESOURCE_LIST_CHANGED_METHOD } from \"../event-map.js\";\nimport { pickFile, pickFiles, action as sendAction } from \"../extensions.js\";\nimport { callToolAsTask } from \"../task-handle.js\";\nimport type {\n App,\n CallToolAsTaskOptions,\n FileResult,\n ModelContext,\n RequestFileOptions,\n TaskHandle,\n Theme,\n ToolCallResult,\n ToolResultData,\n} from \"../types.js\";\nimport { useAppContext } from \"./app-provider.js\";\n\n/** The connected app. Throws outside an `<AppProvider>`. */\nexport function useApp(): App {\n return useAppContext();\n}\n\n// -----------------------------------------------------------------------------\n// Host state\n// -----------------------------------------------------------------------------\n\n/**\n * The current theme, re-rendering only when it actually moves.\n *\n * `App` filters `host-context-changed` notifications through a theme equality\n * check, so a context update that leaves the derived theme untouched (a\n * workspace switch, say) does not re-render every themed component.\n */\nexport function useTheme(): Theme {\n const app = useAppContext();\n const [theme, setTheme] = useState<Theme>(() => app.theme);\n\n useEffect(() => {\n // Sync in case the theme changed between render and effect.\n setTheme(app.theme);\n return app.on(\"theme-changed\", setTheme);\n }, [app]);\n\n return theme;\n}\n\n/**\n * The full ext-apps host context — spec fields (`theme`, `styles`,\n * `displayMode`, `toolInfo`) plus whatever the host publishes alongside.\n * Re-renders on every `host-context-changed`.\n *\n * Prefer `useTheme()` when only theming matters: it filters no-op fires.\n * Reach for this one for host extensions, e.g. on NimbleBrain:\n *\n * ```tsx\n * const { workspace } = useHostContext<{ workspace?: { id: string } }>();\n * ```\n */\nexport function useHostContext<T extends McpUiHostContext = McpUiHostContext>(): T {\n const app = useAppContext();\n const [ctx, setCtx] = useState<T>(() => app.hostContext as T);\n\n useEffect(() => {\n setCtx(app.hostContext as T);\n return app.on(\"host-context-changed\", (c) => setCtx(c as T));\n }, [app]);\n\n return ctx;\n}\n\n/**\n * The latest tool result pushed by the host, or `null` before one arrives. A\n * host that mounts the app without a tool call never sends one, so `null` is a\n * state to render, not only a loading state.\n */\nexport function useToolResult(): ToolResultData | null {\n const app = useAppContext();\n const [data, setData] = useState<ToolResultData | null>(null);\n\n useEffect(() => {\n return app.on(\"tool-result\", setData);\n }, [app]);\n\n return data;\n}\n\n/** The arguments the host is calling the bound tool with, or `null` until it sends them. */\nexport function useToolInput(): Record<string, unknown> | null {\n const app = useAppContext();\n const [input, setInput] = useState<Record<string, unknown> | null>(null);\n\n useEffect(() => {\n return app.on(\"tool-input\", setInput);\n }, [app]);\n\n return input;\n}\n\n/** Report this app's frame size to the host. Every host accepts it; the spec has no capability for it. */\nexport function useResize(): (width?: number, height?: number) => void {\n const app = useAppContext();\n return useCallback((width?: number, height?: number) => app.resize(width, height), [app]);\n}\n\n// -----------------------------------------------------------------------------\n// Tools\n// -----------------------------------------------------------------------------\n\nexport interface UseCallToolResult<TOutput> {\n call: (args?: Record<string, unknown>) => Promise<ToolCallResult<TOutput>>;\n isPending: boolean;\n error: Error | null;\n data: TOutput | null;\n}\n\n/**\n * Call one tool, with pending/error/data state for the latest call.\n *\n * Where the host did not declare `serverTools`, `call` rejects with\n * `HostCapabilityError` without sending, and `error` holds it.\n */\nexport function useCallTool<TOutput = unknown>(toolName: string): UseCallToolResult<TOutput> {\n const app = useAppContext();\n const [isPending, setIsPending] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [data, setData] = useState<TOutput | null>(null);\n const callIdRef = useRef(0);\n\n const call = useCallback(\n async (args?: Record<string, unknown>): Promise<ToolCallResult<TOutput>> => {\n const id = ++callIdRef.current;\n setIsPending(true);\n setError(null);\n\n try {\n const result = await app.callTool<TOutput>(toolName, args);\n // Stale guard: only update if this is still the latest call.\n if (id === callIdRef.current) {\n setData(result.data);\n setIsPending(false);\n }\n return result;\n } catch (err) {\n if (id === callIdRef.current) {\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n setIsPending(false);\n }\n throw err;\n }\n },\n [app, toolName],\n );\n\n return { call, isPending, error, data };\n}\n\n/**\n * Run `callback` when the app's own MCP server announces that its data changed.\n *\n * A server sends `notifications/resources/list_changed` from the write that\n * changed its data, and an MCP Apps host forwards it to that server's views\n * (host capability `serverResources.listChanged`). The callback receives the\n * notification's params as the spec defines them, or `{}` when the host sends\n * none. They name no server and no tool: the notification only ever comes from\n * this app's own server, and a change need not come from a tool call at all.\n *\n * Where the host did not declare `serverResources.listChanged`, the callback\n * never runs. Nothing fails: the app shows what it last loaded until the user\n * or the app reloads it.\n */\nexport function useDataSync(\n callback: (params: NonNullable<ResourceListChangedNotification[\"params\"]>) => void,\n): void {\n const app = useAppContext();\n const callbackRef = useRef(callback);\n callbackRef.current = callback;\n\n useEffect(() => {\n return app.on(RESOURCE_LIST_CHANGED_METHOD, (params) => callbackRef.current(params ?? {}));\n }, [app]);\n}\n\n// -----------------------------------------------------------------------------\n// Agent-facing state\n// -----------------------------------------------------------------------------\n\n/** Debounce window for `useModelContext`, in milliseconds. */\nconst MODEL_CONTEXT_DEBOUNCE_MS = 250;\n\n/**\n * Push what the user is looking at to the agent (ext-apps\n * `ui/update-model-context`), debounced so a selection the user drags through\n * costs one frame rather than thirty.\n *\n * **Declarative** — pushes when `deps` change:\n * ```tsx\n * useModelContext(() => ({\n * state: { board: selectedBoard },\n * summary: `Viewing \"${selectedBoard?.name}\"`,\n * }), [selectedBoard]);\n * ```\n *\n * **Imperative** — returns a push function:\n * ```tsx\n * const push = useModelContext();\n * push({ board: selectedBoard }, \"Viewing board X\");\n * ```\n *\n * The debounce lives here rather than on `app.updateModelContext`, which\n * sends immediately: the rapid-change problem is a React one, and the plain\n * method should do what it says.\n *\n * A no-op where the host did not declare `updateModelContext`.\n */\nexport function useModelContext(): (state: Record<string, unknown>, summary?: string) => void;\nexport function useModelContext(factory: () => ModelContext, deps: unknown[]): void;\nexport function useModelContext(\n factory?: () => ModelContext,\n deps?: unknown[],\n): ((state: Record<string, unknown>, summary?: string) => void) | undefined {\n const app = useAppContext();\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const push = useCallback(\n (state: Record<string, unknown>, summary?: string) => {\n if (timerRef.current) clearTimeout(timerRef.current);\n timerRef.current = setTimeout(() => {\n timerRef.current = null;\n app.updateModelContext(state, summary);\n }, MODEL_CONTEXT_DEBOUNCE_MS);\n },\n [app],\n );\n\n useEffect(() => {\n return () => {\n if (timerRef.current) clearTimeout(timerRef.current);\n };\n }, []);\n\n // Declarative mode: push when deps change. The deps array is\n // caller-provided (mirrors useMemo/useEffect).\n const factoryRef = useRef(factory);\n factoryRef.current = factory;\n useEffect(() => {\n if (!factoryRef.current) return;\n const { state, summary } = factoryRef.current();\n push(state, summary);\n }, [...(deps ?? []), push]);\n\n if (!factory) return push;\n}\n\n/**\n * Send a user message into the agent conversation. A no-op where the host did\n * not declare `message`; check `app.hostCapabilities.message` to decide whether\n * to offer the control at all.\n */\nexport function useSendMessage(): (\n text: string,\n context?: { action?: string; entity?: string },\n) => void {\n const app = useAppContext();\n return useCallback(\n (text: string, context?: { action?: string; entity?: string }) =>\n app.sendMessage(text, context),\n [app],\n );\n}\n\n// -----------------------------------------------------------------------------\n// NimbleBrain host extensions\n// -----------------------------------------------------------------------------\n\n/** Trigger a host-side action. A no-op where the host did not declare `ai.nimblebrain/action`. */\nexport function useAction(): (name: string, params?: Record<string, unknown>) => void {\n const app = useAppContext();\n return useCallback(\n (name: string, params?: Record<string, unknown>) => sendAction(app, name, params),\n [app],\n );\n}\n\nexport interface UseFileUploadResult {\n pickFile: (options?: RequestFileOptions) => Promise<FileResult | null>;\n pickFiles: (options?: RequestFileOptions) => Promise<FileResult[]>;\n isPending: boolean;\n}\n\n/**\n * The host's native file picker, with a pending flag. Both functions reject\n * with `HostCapabilityError` where the host did not declare\n * `ai.nimblebrain/request-file`; `hostSupports(app, \"requestFile\")` says so up\n * front.\n */\nexport function useFileUpload(): UseFileUploadResult {\n const app = useAppContext();\n const [isPending, setIsPending] = useState(false);\n\n const one = useCallback(\n async (options?: RequestFileOptions) => {\n setIsPending(true);\n try {\n return await pickFile(app, options);\n } finally {\n setIsPending(false);\n }\n },\n [app],\n );\n\n const many = useCallback(\n async (options?: RequestFileOptions) => {\n setIsPending(true);\n try {\n return await pickFiles(app, options);\n } finally {\n setIsPending(false);\n }\n },\n [app],\n );\n\n return { pickFile: one, pickFiles: many, isPending };\n}\n\n// -----------------------------------------------------------------------------\n// useCallToolAsTask — lifecycle wrapper around `callToolAsTask(app, …)`\n// -----------------------------------------------------------------------------\n\n/**\n * Spec terminal status values for the MCP 2025-11-25 tasks utility.\n *\n * Typed via `satisfies TaskStatus` so a rename of any member of the\n * spec enum (`completed`/`failed`/`cancelled`) trips `tsc` — we never\n * hand-type these as bare string literals in comparisons.\n */\nconst COMPLETED_STATUS = \"completed\" satisfies TaskStatus;\nconst FAILED_STATUS = \"failed\" satisfies TaskStatus;\nconst CANCELLED_STATUS = \"cancelled\" satisfies TaskStatus;\n\nconst TERMINAL_STATUSES: ReadonlySet<TaskStatus> = new Set<TaskStatus>([\n COMPLETED_STATUS,\n FAILED_STATUS,\n CANCELLED_STATUS,\n]);\n\n/**\n * Fallback poll cadence used when the receiver's `CreateTaskResult.task`\n * carries no `pollInterval`. The effective fire delay is this value × 1.5\n * ≈ 7.5s, well below default TTLs but long enough to avoid hammering hosts\n * that do emit `notifications/tasks/status`.\n */\nconst DEFAULT_POLL_INTERVAL_MS = 5_000;\nconst POLL_FALLBACK_MULTIPLIER = 1.5;\n\n/**\n * Stop the poll fallback after this many consecutive `refresh()` failures.\n * Bridge teardown / TTL eviction / network outage all manifest as repeated\n * `tasks/get` rejections. Without a guard the timer re-arms forever; with\n * it we stop polling silently after `MAX_REFRESH_FAILURES` strikes — the\n * blocking `result()` path remains the authoritative source of truth, so\n * giving up on polling never loses the terminal value.\n */\nconst MAX_REFRESH_FAILURES = 5;\n\nexport interface UseCallToolAsTaskResult<TInput, TOutput> {\n /**\n * Start (or re-start) a task-augmented tool call. Returns the\n * resolved `TaskHandle` so callers can `await fire(...)` if they\n * want to know when the server has accepted the task, but reading\n * `task`/`result`/`error` from the hook is usually enough.\n *\n * Re-firing while a previous task is still in flight detaches this\n * hook from the prior handle (stops polling, unsubscribes) but does\n * NOT cancel the server-side task — the task keeps running and its\n * result may still be fetched elsewhere (e.g. on page revisit).\n */\n fire(args?: TInput, options?: CallToolAsTaskOptions): Promise<TaskHandle<TOutput>>;\n /** Latest `Task` state, or `null` before `fire()` has been called. */\n task: Task | null;\n /** Populated once `handle.result()` resolves non-error. */\n result: ToolCallResult<TOutput> | null;\n /** Populated on rejection or when `result.isError === true`. */\n error: Error | null;\n /** `true` while the task is non-terminal (`working` / `input_required`). */\n isWorking: boolean;\n /** `true` when `task.status ∈ {completed, failed, cancelled}`. */\n isTerminal: boolean;\n /**\n * Cancel the active task via `tasks/cancel`. No-op when no task is\n * active. Swallowed errors surface via `error`.\n */\n cancel(): Promise<void>;\n}\n\n/**\n * React wrapper around `callToolAsTask(app, …)`.\n *\n * Handles the full MCP 2025-11-25 task lifecycle:\n *\n * 1. `fire(args, options?)` sends the task-augmented `tools/call` and\n * stores the returned `TaskHandle` in a ref.\n * 2. Subscribes to `handle.onStatus` — updates `task` whenever the\n * host emits `notifications/tasks/status` (OPTIONAL per spec).\n * 3. Starts a polling fallback: if no status notification arrives\n * within `pollInterval × 1.5` (defaulting to ~7.5s), calls\n * `handle.refresh()` for canonical state. Stops on terminal.\n * 4. Awaits `handle.result()` in the background — resolves to either\n * `result` (success / `isError: false`) or `error` (network reject\n * OR `result.isError === true`).\n *\n * Where the host did not declare the tasks capability, `fire` rejects with\n * `HostCapabilityError` and `error` holds it; `app.supportsTasks` says so up\n * front.\n *\n * Cleanup (unmount or re-fire) unsubscribes from status events and\n * clears the poll timer, but does NOT cancel the server-side task —\n * the caller may remount and recover state by firing again, and tasks\n * outlive iframe teardown until TTL elapses.\n */\nexport function useCallToolAsTask<TInput = Record<string, unknown>, TOutput = unknown>(\n toolName: string,\n): UseCallToolAsTaskResult<TInput, TOutput> {\n const app = useAppContext();\n\n const [task, setTask] = useState<Task | null>(null);\n const [result, setResult] = useState<ToolCallResult<TOutput> | null>(null);\n const [error, setError] = useState<Error | null>(null);\n\n // Per-fire generation counter. Every `fire()` increments; any\n // asynchronous callback (status listener, poll timer, `result()`\n // resolution) captures the gen at schedule time and bails if the\n // current gen has moved past it. This is the single source of truth\n // for \"is this work still relevant?\" — more robust than comparing\n // TaskHandle identity because handles can be detached by re-fire.\n const genRef = useRef(0);\n\n // Active handle + its teardown handles. We keep both in refs so the\n // hook's stable `fire`/`cancel` callbacks can reach the current\n // lifecycle state without re-binding on every render.\n const handleRef = useRef<TaskHandle<TOutput> | null>(null);\n const unsubscribeRef = useRef<(() => void) | null>(null);\n const pollTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n // `pollInterval × 1.5`, captured per-fire. Falls back to the 5s\n // default when the host didn't provide a `pollInterval` in the\n // initial CreateTaskResult.task.\n const pollDelayRef = useRef<number>(DEFAULT_POLL_INTERVAL_MS * POLL_FALLBACK_MULTIPLIER);\n\n // Track the latest known status out-of-band so the poll callback\n // can decide whether to keep polling without depending on the\n // `task` React state (which lags a render behind setState).\n const terminalRef = useRef<boolean>(false);\n\n // Consecutive `refresh()` failure count for the active fire. Reset\n // on every successful refresh, status notification, or new fire.\n const refreshFailureCountRef = useRef<number>(0);\n\n const clearPollTimer = useCallback(() => {\n if (pollTimerRef.current !== null) {\n clearTimeout(pollTimerRef.current);\n pollTimerRef.current = null;\n }\n }, []);\n\n const detachCurrent = useCallback(() => {\n clearPollTimer();\n if (unsubscribeRef.current) {\n unsubscribeRef.current();\n unsubscribeRef.current = null;\n }\n handleRef.current = null;\n }, [clearPollTimer]);\n\n const scheduleNextPoll = useCallback(\n (gen: number) => {\n clearPollTimer();\n if (terminalRef.current) return;\n pollTimerRef.current = setTimeout(() => {\n // Bail if this fire has been superseded or torn down.\n if (gen !== genRef.current) return;\n const h = handleRef.current;\n if (!h) return;\n if (terminalRef.current) return;\n // `refresh()` is the canonical source for `createdAt` /\n // `lastUpdatedAt` / `ttl` — notification-derived Tasks carry\n // placeholders per the router in `task-handle.ts`.\n h.refresh().then(\n (fresh) => {\n if (gen !== genRef.current) return;\n refreshFailureCountRef.current = 0;\n setTask(fresh);\n const isTerminal = TERMINAL_STATUSES.has(fresh.status);\n terminalRef.current = isTerminal;\n if (!isTerminal) scheduleNextPoll(gen);\n },\n () => {\n // Swallow refresh errors — the blocking `tasks/result` is\n // the authoritative path; polling is best-effort. But guard\n // against runaway re-arming: if the bridge is gone or the\n // task TTL has elapsed, every refresh rejects. Stop after\n // MAX_REFRESH_FAILURES consecutive strikes; result() will\n // still surface a terminal value or error when it settles.\n if (gen !== genRef.current) return;\n refreshFailureCountRef.current += 1;\n if (refreshFailureCountRef.current >= MAX_REFRESH_FAILURES) return;\n if (!terminalRef.current) scheduleNextPoll(gen);\n },\n );\n }, pollDelayRef.current);\n },\n [clearPollTimer],\n );\n\n const fire = useCallback(\n async (args?: TInput, options?: CallToolAsTaskOptions): Promise<TaskHandle<TOutput>> => {\n // Detach any in-flight prior task BEFORE incrementing the gen so\n // its callbacks see the new gen and bail. (Incrementing then\n // detaching would also work, but detach-first makes the order\n // obvious: stop listening, bump generation, start fresh.)\n detachCurrent();\n const gen = ++genRef.current;\n\n // Reset per-fire state. Don't wipe `task` yet — `callToolAsTask`\n // is async; showing the previous terminal state briefly is less\n // jarring than flicker to null → working. We clear on resolution.\n setResult(null);\n setError(null);\n terminalRef.current = false;\n refreshFailureCountRef.current = 0;\n\n let handle: TaskHandle<TOutput>;\n try {\n handle = await callToolAsTask<TOutput>(app, toolName, args, options);\n } catch (err) {\n if (gen !== genRef.current) throw err;\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n throw err;\n }\n\n // Caller superseded the fire between request and response —\n // don't attach listeners, but still return the handle so the\n // awaiter can observe it.\n if (gen !== genRef.current) return handle;\n\n handleRef.current = handle;\n\n // Derive the fallback poll delay from the receiver's advertised\n // `pollInterval`. Spec allows it to be absent; we then use the\n // 5s default.\n const hintedInterval = handle.task.pollInterval;\n pollDelayRef.current =\n typeof hintedInterval === \"number\" && hintedInterval > 0\n ? hintedInterval * POLL_FALLBACK_MULTIPLIER\n : DEFAULT_POLL_INTERVAL_MS * POLL_FALLBACK_MULTIPLIER;\n\n setTask(handle.task);\n terminalRef.current = TERMINAL_STATUSES.has(handle.task.status);\n\n // Subscribe to `notifications/tasks/status`. Each notification\n // resets the poll countdown (that's the whole point of the\n // \"notification OR polling\" contract — if notifications flow,\n // we don't poll; if they don't, the timer fires).\n unsubscribeRef.current = handle.onStatus((updated) => {\n if (gen !== genRef.current) return;\n refreshFailureCountRef.current = 0;\n setTask(updated);\n const isTerminal = TERMINAL_STATUSES.has(updated.status);\n terminalRef.current = isTerminal;\n if (isTerminal) {\n clearPollTimer();\n } else {\n scheduleNextPoll(gen);\n }\n });\n\n // Kick off the blocking result fetch — this is the authoritative\n // terminal value regardless of whether notifications or polls\n // landed in between. By spec, `tasks/result` blocks until the task\n // reaches a terminal status, so when this settles we KNOW the task\n // is terminal — stop polling and synthesize a terminal `task`\n // status so derived flags (`isTerminal`, `isWorking`) match the\n // populated `result` / `error` immediately.\n handle.result().then(\n (res) => {\n if (gen !== genRef.current) return;\n terminalRef.current = true;\n clearPollTimer();\n // Synthesize the terminal Task: failed if `isError`, otherwise\n // completed. The next status notification or refresh would\n // confirm this, but we want internal state consistent the\n // instant `result` is populated — a \"result populated while\n // isWorking=true\" render is incoherent for consumers.\n setTask((prev) => {\n const status: TaskStatus = res.isError ? FAILED_STATUS : COMPLETED_STATUS;\n const now = new Date().toISOString();\n return prev\n ? { ...prev, status, lastUpdatedAt: now }\n : {\n taskId: handle.task.taskId,\n status,\n ttl: handle.task.ttl,\n createdAt: handle.task.createdAt,\n lastUpdatedAt: now,\n };\n });\n if (res.isError) {\n // Spec: `CallToolResult.isError === true` is a tool-level\n // error, not a protocol error. Surface via `error` for\n // consumers who treat it as a failure, but also populate\n // `result` so callers inspecting the raw content block\n // still have access.\n setResult(res);\n const msg =\n typeof res.data === \"string\" && res.data.length > 0\n ? res.data\n : `Tool \"${toolName}\" returned isError: true`;\n setError(new Error(msg));\n } else {\n setResult(res);\n }\n },\n (err) => {\n if (gen !== genRef.current) return;\n // result() rejection means the `tasks/result` RPC failed\n // (transport error, taskId not found, bridge teardown). We\n // can't know the server-side task's actual final state, but\n // we know polling won't recover here either — same transport.\n // Mark terminal and synthesize `failed` status for UX\n // coherence; the populated `error` tells the consumer what\n // specifically went wrong.\n terminalRef.current = true;\n clearPollTimer();\n setTask((prev) => {\n const now = new Date().toISOString();\n return prev\n ? { ...prev, status: FAILED_STATUS, lastUpdatedAt: now }\n : {\n taskId: handle.task.taskId,\n status: FAILED_STATUS,\n ttl: handle.task.ttl,\n createdAt: handle.task.createdAt,\n lastUpdatedAt: now,\n };\n });\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n },\n );\n\n // Start the poll fallback only if we aren't already terminal.\n if (!terminalRef.current) scheduleNextPoll(gen);\n\n return handle;\n },\n [app, toolName, detachCurrent, clearPollTimer, scheduleNextPoll],\n );\n\n const cancel = useCallback(async (): Promise<void> => {\n const h = handleRef.current;\n if (!h) return;\n const gen = genRef.current;\n try {\n const cancelled = await h.cancel();\n if (gen !== genRef.current) return;\n setTask(cancelled);\n terminalRef.current = TERMINAL_STATUSES.has(cancelled.status);\n clearPollTimer();\n } catch (err) {\n if (gen !== genRef.current) return;\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n }\n }, [clearPollTimer]);\n\n // Cleanup on unmount: stop polling, drop the status subscription.\n // Deliberately do NOT call `handle.cancel()` — the server-side task\n // keeps running so a remount can recover state.\n useEffect(() => {\n return () => {\n genRef.current += 1;\n if (pollTimerRef.current !== null) {\n clearTimeout(pollTimerRef.current);\n pollTimerRef.current = null;\n }\n if (unsubscribeRef.current) {\n unsubscribeRef.current();\n unsubscribeRef.current = null;\n }\n handleRef.current = null;\n };\n }, []);\n\n const isTerminal = task !== null && TERMINAL_STATUSES.has(task.status);\n const isWorking = task !== null && !isTerminal;\n\n return { fire, task, result, error, isWorking, isTerminal, cancel };\n}\n"]}
|