skybridge 0.0.0-dev.8eed82d → 0.0.0-dev.8f4a954
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 +8 -8
- package/dist/cli/detect-port.js.map +1 -1
- package/dist/cli/header.js.map +1 -1
- package/dist/cli/run-command.js.map +1 -1
- package/dist/cli/telemetry.js.map +1 -1
- package/dist/cli/tunnel-control-server.d.ts +9 -0
- package/dist/cli/tunnel-control-server.js +31 -0
- package/dist/cli/tunnel-control-server.js.map +1 -0
- package/dist/cli/tunnel-control-server.test.js +39 -0
- package/dist/cli/tunnel-control-server.test.js.map +1 -0
- package/dist/cli/tunnel-handler.d.ts +3 -0
- package/dist/cli/tunnel-handler.js +48 -0
- package/dist/cli/tunnel-handler.js.map +1 -0
- package/dist/cli/tunnel-handler.test.js +105 -0
- package/dist/cli/tunnel-handler.test.js.map +1 -0
- package/dist/cli/tunnel.d.ts +57 -0
- package/dist/cli/tunnel.js +154 -0
- package/dist/cli/tunnel.js.map +1 -0
- package/dist/cli/tunnel.test.js +190 -0
- package/dist/cli/tunnel.test.js.map +1 -0
- package/dist/cli/types.js.map +1 -1
- package/dist/cli/use-execute-steps.js.map +1 -1
- package/dist/cli/use-messages.js.map +1 -1
- package/dist/cli/use-nodemon.js +11 -2
- package/dist/cli/use-nodemon.js.map +1 -1
- package/dist/cli/use-open-browser.d.ts +1 -0
- package/dist/cli/use-open-browser.js +44 -0
- package/dist/cli/use-open-browser.js.map +1 -0
- package/dist/cli/use-tunnel.d.ts +1 -1
- package/dist/cli/use-tunnel.js +102 -68
- package/dist/cli/use-tunnel.js.map +1 -1
- package/dist/cli/use-typescript-check.d.ts +1 -0
- package/dist/cli/use-typescript-check.js +41 -6
- package/dist/cli/use-typescript-check.js.map +1 -1
- package/dist/commands/build.js +62 -7
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/dev.d.ts +1 -0
- package/dist/commands/dev.js +33 -2
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/telemetry/disable.js.map +1 -1
- package/dist/commands/telemetry/enable.js.map +1 -1
- package/dist/commands/telemetry/status.js.map +1 -1
- package/dist/server/asset-base-url-transform-plugin.js +1 -1
- package/dist/server/asset-base-url-transform-plugin.js.map +1 -1
- package/dist/server/asset-base-url-transform-plugin.test.js +29 -0
- package/dist/server/asset-base-url-transform-plugin.test.js.map +1 -1
- package/dist/server/content-helpers.js.map +1 -1
- package/dist/server/content-helpers.test.js +1 -1
- package/dist/server/content-helpers.test.js.map +1 -1
- package/dist/server/express.d.ts +1 -5
- package/dist/server/express.js +34 -10
- package/dist/server/express.js.map +1 -1
- package/dist/server/express.test.js +249 -71
- package/dist/server/express.test.js.map +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/inferUtilityTypes.d.ts +6 -6
- package/dist/server/inferUtilityTypes.js.map +1 -1
- package/dist/server/metric.js.map +1 -1
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/middleware.test-d.js.map +1 -1
- package/dist/server/middleware.test.js.map +1 -1
- package/dist/server/server.d.ts +90 -40
- package/dist/server/server.js +150 -77
- package/dist/server/server.js.map +1 -1
- package/dist/server/templateHelper.d.ts +5 -7
- package/dist/server/templateHelper.js +3 -22
- package/dist/server/templateHelper.js.map +1 -1
- package/dist/server/templates.generated.d.ts +4 -0
- package/dist/server/templates.generated.js +47 -0
- package/dist/server/templates.generated.js.map +1 -0
- package/dist/server/tunnel-proxy-router.d.ts +7 -0
- package/dist/server/tunnel-proxy-router.js +110 -0
- package/dist/server/tunnel-proxy-router.js.map +1 -0
- package/dist/server/tunnel-proxy-router.test.js +229 -0
- package/dist/server/tunnel-proxy-router.test.js.map +1 -0
- package/dist/server/viewsDevServer.d.ts +14 -0
- package/dist/server/{widgetsDevServer.js → viewsDevServer.js} +17 -5
- package/dist/server/viewsDevServer.js.map +1 -0
- package/dist/test/utils.d.ts +7 -7
- package/dist/test/utils.js +21 -21
- package/dist/test/utils.js.map +1 -1
- package/dist/test/view.test.d.ts +1 -0
- package/dist/test/{widget.test.js → view.test.js} +128 -59
- package/dist/test/view.test.js.map +1 -0
- package/dist/version.js +1 -3
- package/dist/version.js.map +1 -1
- package/dist/web/bridges/apps-sdk/adaptor.d.ts +5 -3
- package/dist/web/bridges/apps-sdk/adaptor.js +19 -4
- package/dist/web/bridges/apps-sdk/adaptor.js.map +1 -1
- package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
- package/dist/web/bridges/apps-sdk/index.js.map +1 -1
- package/dist/web/bridges/apps-sdk/types.d.ts +8 -1
- package/dist/web/bridges/apps-sdk/types.js.map +1 -1
- package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -1
- package/dist/web/bridges/get-adaptor.js.map +1 -1
- package/dist/web/bridges/index.js.map +1 -1
- package/dist/web/bridges/mcp-app/adaptor.d.ts +9 -7
- package/dist/web/bridges/mcp-app/adaptor.js +33 -30
- package/dist/web/bridges/mcp-app/adaptor.js.map +1 -1
- package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
- package/dist/web/bridges/mcp-app/index.js.map +1 -1
- package/dist/web/bridges/mcp-app/types.js.map +1 -1
- package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
- package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -1
- package/dist/web/bridges/types.d.ts +17 -9
- package/dist/web/bridges/types.js.map +1 -1
- package/dist/web/bridges/use-host-context.js.map +1 -1
- package/dist/web/components/modal-provider.js +1 -1
- package/dist/web/components/modal-provider.js.map +1 -1
- package/dist/web/create-store.js +9 -9
- package/dist/web/create-store.js.map +1 -1
- package/dist/web/create-store.test.js +14 -16
- package/dist/web/create-store.test.js.map +1 -1
- package/dist/web/data-llm.d.ts +1 -1
- package/dist/web/data-llm.js +3 -3
- package/dist/web/data-llm.js.map +1 -1
- package/dist/web/data-llm.test.js +22 -22
- package/dist/web/data-llm.test.js.map +1 -1
- package/dist/web/generate-helpers.d.ts +14 -14
- package/dist/web/generate-helpers.js +14 -14
- package/dist/web/generate-helpers.js.map +1 -1
- package/dist/web/generate-helpers.test-d.js +26 -26
- package/dist/web/generate-helpers.test-d.js.map +1 -1
- package/dist/web/generate-helpers.test.js.map +1 -1
- package/dist/web/helpers/state.d.ts +2 -2
- package/dist/web/helpers/state.js +11 -11
- package/dist/web/helpers/state.js.map +1 -1
- package/dist/web/helpers/state.test.js +9 -9
- package/dist/web/helpers/state.test.js.map +1 -1
- package/dist/web/hooks/index.d.ts +3 -1
- package/dist/web/hooks/index.js +3 -1
- package/dist/web/hooks/index.js.map +1 -1
- package/dist/web/hooks/test/utils.js.map +1 -1
- package/dist/web/hooks/use-call-tool.js.map +1 -1
- package/dist/web/hooks/use-call-tool.test-d.js.map +1 -1
- package/dist/web/hooks/use-call-tool.test.js +0 -4
- package/dist/web/hooks/use-call-tool.test.js.map +1 -1
- package/dist/web/hooks/use-display-mode.js.map +1 -1
- package/dist/web/hooks/use-display-mode.test-d.js.map +1 -1
- package/dist/web/hooks/use-display-mode.test.js.map +1 -1
- package/dist/web/hooks/use-files.js.map +1 -1
- package/dist/web/hooks/use-files.test.js.map +1 -1
- package/dist/web/hooks/use-layout.js.map +1 -1
- package/dist/web/hooks/use-layout.test.js.map +1 -1
- package/dist/web/hooks/use-open-external.js.map +1 -1
- package/dist/web/hooks/use-open-external.test.js.map +1 -1
- package/dist/web/hooks/use-request-close.d.ts +2 -0
- package/dist/web/hooks/use-request-close.js +8 -0
- package/dist/web/hooks/use-request-close.js.map +1 -0
- package/dist/web/hooks/use-request-close.test.d.ts +1 -0
- package/dist/web/hooks/use-request-close.test.js +52 -0
- package/dist/web/hooks/use-request-close.test.js.map +1 -0
- package/dist/web/hooks/use-request-modal.d.ts +1 -1
- package/dist/web/hooks/use-request-modal.js +4 -4
- package/dist/web/hooks/use-request-modal.js.map +1 -1
- package/dist/web/hooks/use-request-modal.test.js +1 -1
- package/dist/web/hooks/use-request-modal.test.js.map +1 -1
- package/dist/web/hooks/use-request-size.d.ts +3 -0
- package/dist/web/hooks/use-request-size.js +8 -0
- package/dist/web/hooks/use-request-size.js.map +1 -0
- package/dist/web/hooks/use-request-size.test.d.ts +1 -0
- package/dist/web/hooks/use-request-size.test.js +65 -0
- package/dist/web/hooks/use-request-size.test.js.map +1 -0
- package/dist/web/hooks/use-send-follow-up-message.d.ts +2 -1
- package/dist/web/hooks/use-send-follow-up-message.js +2 -2
- package/dist/web/hooks/use-send-follow-up-message.js.map +1 -1
- package/dist/web/hooks/use-set-open-in-app-url.js.map +1 -1
- package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
- package/dist/web/hooks/use-tool-info.js.map +1 -1
- package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
- package/dist/web/hooks/use-tool-info.test.js.map +1 -1
- package/dist/web/hooks/use-user.js.map +1 -1
- package/dist/web/hooks/use-user.test.js.map +1 -1
- package/dist/web/hooks/use-view-state.d.ts +4 -0
- package/dist/web/hooks/use-view-state.js +32 -0
- package/dist/web/hooks/use-view-state.js.map +1 -0
- package/dist/web/hooks/use-view-state.test.d.ts +1 -0
- package/dist/web/hooks/{use-widget-state.test.js → use-view-state.test.js} +17 -17
- package/dist/web/hooks/use-view-state.test.js.map +1 -0
- package/dist/web/index.d.ts +1 -3
- package/dist/web/index.js +1 -2
- package/dist/web/index.js.map +1 -1
- package/dist/web/mount-view.d.ts +1 -0
- package/dist/web/{mount-widget.js → mount-view.js} +2 -2
- package/dist/web/mount-view.js.map +1 -0
- package/dist/web/plugin/data-llm.test.js.map +1 -1
- package/dist/web/plugin/plugin.d.ts +1 -1
- package/dist/web/plugin/plugin.js +87 -41
- package/dist/web/plugin/plugin.js.map +1 -1
- package/dist/web/plugin/scan-views.d.ts +16 -0
- package/dist/web/plugin/scan-views.js +88 -0
- package/dist/web/plugin/scan-views.js.map +1 -0
- package/dist/web/plugin/scan-views.test.d.ts +1 -0
- package/dist/web/plugin/scan-views.test.js +99 -0
- package/dist/web/plugin/scan-views.test.js.map +1 -0
- package/dist/web/plugin/transform-data-llm.js.map +1 -1
- package/dist/web/plugin/transform-data-llm.test.js.map +1 -1
- package/dist/web/plugin/{validate-widget.js → validate-view.js} +1 -1
- package/dist/web/plugin/validate-view.js.map +1 -0
- package/dist/web/plugin/validate-view.test.d.ts +1 -0
- package/dist/web/plugin/{validate-widget.test.js → validate-view.test.js} +6 -6
- package/dist/web/plugin/validate-view.test.js.map +1 -0
- package/dist/web/proxy.js.map +1 -1
- package/dist/web/types.js.map +1 -1
- package/package.json +26 -20
- package/dist/server/templates/development.hbs +0 -12
- package/dist/server/templates/production.hbs +0 -6
- package/dist/server/widgetsDevServer.d.ts +0 -3
- package/dist/server/widgetsDevServer.js.map +0 -1
- package/dist/test/widget.test.js.map +0 -1
- package/dist/web/hooks/use-widget-state.d.ts +0 -4
- package/dist/web/hooks/use-widget-state.js +0 -32
- package/dist/web/hooks/use-widget-state.js.map +0 -1
- package/dist/web/hooks/use-widget-state.test.js.map +0 -1
- package/dist/web/mount-widget.d.ts +0 -1
- package/dist/web/mount-widget.js.map +0 -1
- package/dist/web/plugin/scan-widgets.d.ts +0 -8
- package/dist/web/plugin/scan-widgets.js +0 -68
- package/dist/web/plugin/scan-widgets.js.map +0 -1
- package/dist/web/plugin/scan-widgets.test.js +0 -96
- package/dist/web/plugin/scan-widgets.test.js.map +0 -1
- package/dist/web/plugin/validate-widget.js.map +0 -1
- package/dist/web/plugin/validate-widget.test.js.map +0 -1
- /package/dist/{test/widget.test.d.ts → cli/tunnel-control-server.test.d.ts} +0 -0
- /package/dist/{web/hooks/use-widget-state.test.d.ts → cli/tunnel-handler.test.d.ts} +0 -0
- /package/dist/{web/plugin/scan-widgets.test.d.ts → cli/tunnel.test.d.ts} +0 -0
- /package/dist/{web/plugin/validate-widget.test.d.ts → server/tunnel-proxy-router.test.d.ts} +0 -0
- /package/dist/web/plugin/{validate-widget.d.ts → validate-view.d.ts} +0 -0
|
@@ -2,18 +2,30 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it, vi, } from "vitest";
|
|
3
3
|
import { createMockExtra, createMockMcpServer, resetTestEnv, setTestEnv, } from "./utils.js";
|
|
4
4
|
const mockManifest = {
|
|
5
|
-
"skybridge:
|
|
6
|
-
file: "assets/my-
|
|
7
|
-
name: "my-
|
|
5
|
+
"skybridge:view:my-view": {
|
|
6
|
+
file: "assets/my-view-abc123.js",
|
|
7
|
+
name: "my-view",
|
|
8
8
|
isEntry: true,
|
|
9
9
|
},
|
|
10
|
-
"skybridge:
|
|
11
|
-
file: "assets/folder-
|
|
12
|
-
name: "folder-
|
|
10
|
+
"skybridge:view:folder-view": {
|
|
11
|
+
file: "assets/folder-view-def456.js",
|
|
12
|
+
name: "folder-view",
|
|
13
13
|
isEntry: true,
|
|
14
14
|
},
|
|
15
15
|
"style.css": { file: "style.css" },
|
|
16
16
|
};
|
|
17
|
+
// Mirrors `McpServer.computeViewVersionParam`. Tests recompute the expected
|
|
18
|
+
// hash from the mocked manifest so they don't hardcode digest output.
|
|
19
|
+
function expectedVersionParam(viewFile, styleFile) {
|
|
20
|
+
const hash = crypto
|
|
21
|
+
.createHash("sha256")
|
|
22
|
+
.update(viewFile)
|
|
23
|
+
.update("\0")
|
|
24
|
+
.update(styleFile)
|
|
25
|
+
.digest("hex")
|
|
26
|
+
.slice(0, 8);
|
|
27
|
+
return `?v=${hash}`;
|
|
28
|
+
}
|
|
17
29
|
const actual = vi.hoisted(() => require("node:fs"));
|
|
18
30
|
vi.mock("node:fs", () => {
|
|
19
31
|
const readFileSyncImpl = (path, ...args) => {
|
|
@@ -45,11 +57,11 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
45
57
|
it("should generate correct HTML for development mode", async () => {
|
|
46
58
|
setTestEnv({ NODE_ENV: "development" });
|
|
47
59
|
server.registerTool({
|
|
48
|
-
name: "my-
|
|
60
|
+
name: "my-view",
|
|
49
61
|
description: "Test tool",
|
|
50
62
|
view: {
|
|
51
|
-
component: "my-
|
|
52
|
-
description: "Test
|
|
63
|
+
component: "my-view",
|
|
64
|
+
description: "Test view",
|
|
53
65
|
},
|
|
54
66
|
}, vi.fn());
|
|
55
67
|
const appsSdkResourceCallback = mockRegisterResource.mock
|
|
@@ -59,12 +71,12 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
59
71
|
const serverUrl = `http://${host}`;
|
|
60
72
|
const hmrUrl = `ws://${host}`;
|
|
61
73
|
const mockExtra = createMockExtra(host);
|
|
62
|
-
const result = await appsSdkResourceCallback(new URL("ui://
|
|
74
|
+
const result = await appsSdkResourceCallback(new URL("ui://views/apps-sdk/my-view.html"), mockExtra);
|
|
63
75
|
expect(mockRegisterTool).toHaveBeenCalled();
|
|
64
76
|
expect(result).toEqual({
|
|
65
77
|
contents: [
|
|
66
78
|
{
|
|
67
|
-
uri: "ui://
|
|
79
|
+
uri: "ui://views/apps-sdk/my-view.html",
|
|
68
80
|
mimeType: "text/html+skybridge",
|
|
69
81
|
text: expect.stringContaining('<div id="root"></div>'),
|
|
70
82
|
_meta: {
|
|
@@ -73,23 +85,23 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
73
85
|
connect_domains: [serverUrl, hmrUrl],
|
|
74
86
|
},
|
|
75
87
|
"openai/widgetDomain": serverUrl,
|
|
76
|
-
"openai/widgetDescription": "Test
|
|
88
|
+
"openai/widgetDescription": "Test view",
|
|
77
89
|
},
|
|
78
90
|
},
|
|
79
91
|
],
|
|
80
92
|
});
|
|
81
93
|
expect(result.contents[0]?.text).toContain(`${serverUrl}/assets/@react-refresh`);
|
|
82
94
|
expect(result.contents[0]?.text).toContain(`${serverUrl}/@vite/client`);
|
|
83
|
-
expect(result.contents[0]?.text).toContain(`${serverUrl}/_skybridge/
|
|
95
|
+
expect(result.contents[0]?.text).toContain(`${serverUrl}/_skybridge/view/my-view`);
|
|
84
96
|
});
|
|
85
97
|
it("should generate correct HTML for production mode", async () => {
|
|
86
98
|
setTestEnv({ NODE_ENV: "production" });
|
|
87
99
|
server.registerTool({
|
|
88
|
-
name: "my-
|
|
100
|
+
name: "my-view",
|
|
89
101
|
description: "Test tool",
|
|
90
102
|
view: {
|
|
91
|
-
component: "my-
|
|
92
|
-
description: "Test
|
|
103
|
+
component: "my-view",
|
|
104
|
+
description: "Test view",
|
|
93
105
|
},
|
|
94
106
|
}, vi.fn());
|
|
95
107
|
const appsSdkResourceCallback = mockRegisterResource.mock
|
|
@@ -98,11 +110,12 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
98
110
|
const host = "myapp.com";
|
|
99
111
|
const serverUrl = `https://${host}`;
|
|
100
112
|
const mockExtra = createMockExtra(host);
|
|
101
|
-
const
|
|
113
|
+
const versionedUri = `ui://views/apps-sdk/my-view.html${expectedVersionParam("assets/my-view-abc123.js", "style.css")}`;
|
|
114
|
+
const result = await appsSdkResourceCallback(new URL(versionedUri), mockExtra);
|
|
102
115
|
expect(result).toEqual({
|
|
103
116
|
contents: [
|
|
104
117
|
{
|
|
105
|
-
uri:
|
|
118
|
+
uri: versionedUri,
|
|
106
119
|
mimeType: "text/html+skybridge",
|
|
107
120
|
text: expect.stringContaining('<div id="root"></div>'),
|
|
108
121
|
_meta: {
|
|
@@ -111,22 +124,22 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
111
124
|
connect_domains: [serverUrl],
|
|
112
125
|
},
|
|
113
126
|
"openai/widgetDomain": serverUrl,
|
|
114
|
-
"openai/widgetDescription": "Test
|
|
127
|
+
"openai/widgetDescription": "Test view",
|
|
115
128
|
},
|
|
116
129
|
},
|
|
117
130
|
],
|
|
118
131
|
});
|
|
119
132
|
expect(result.contents[0]?.text).not.toContain(`${serverUrl}/assets/@react-refresh`);
|
|
120
133
|
expect(result.contents[0]?.text).not.toContain(`${serverUrl}@vite/client`);
|
|
121
|
-
expect(result.contents[0]?.text).toContain(`${serverUrl}/assets/assets/my-
|
|
134
|
+
expect(result.contents[0]?.text).toContain(`${serverUrl}/assets/assets/my-view-abc123.js`);
|
|
122
135
|
expect(result.contents[0]?.text).toContain(`${serverUrl}/assets/style.css`);
|
|
123
136
|
});
|
|
124
|
-
it("should prefer x-alpic-forwarded-url when hashing Claude
|
|
137
|
+
it("should prefer x-alpic-forwarded-url when hashing Claude view domains", async () => {
|
|
125
138
|
setTestEnv({ NODE_ENV: "production" });
|
|
126
139
|
server.registerTool({
|
|
127
|
-
name: "my-
|
|
140
|
+
name: "my-view",
|
|
128
141
|
description: "Test tool",
|
|
129
|
-
view: { component: "my-
|
|
142
|
+
view: { component: "my-view", description: "Test view" },
|
|
130
143
|
}, vi.fn());
|
|
131
144
|
const extAppsResourceCallback = mockRegisterResource.mock
|
|
132
145
|
.calls[1]?.[3];
|
|
@@ -137,7 +150,7 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
137
150
|
.update(forwardedUrl)
|
|
138
151
|
.digest("hex")
|
|
139
152
|
.slice(0, 32)}.claudemcpcontent.com`;
|
|
140
|
-
const result = await extAppsResourceCallback(new URL(
|
|
153
|
+
const result = await extAppsResourceCallback(new URL(`ui://views/ext-apps/my-view.html${expectedVersionParam("assets/my-view-abc123.js", "style.css")}`), createMockExtra("localhost:3000", {
|
|
141
154
|
headers: {
|
|
142
155
|
"user-agent": "Claude-User",
|
|
143
156
|
"x-alpic-forwarded-url": forwardedUrl,
|
|
@@ -151,18 +164,18 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
151
164
|
connectDomains: ["http://localhost:3000"],
|
|
152
165
|
baseUriDomains: ["http://localhost:3000"],
|
|
153
166
|
},
|
|
154
|
-
description: "Test
|
|
167
|
+
description: "Test view",
|
|
155
168
|
domain: expectedDomain,
|
|
156
169
|
},
|
|
157
170
|
});
|
|
158
171
|
});
|
|
159
172
|
it("should register resources with correct hostType for both apps-sdk and ext-apps", async () => {
|
|
160
173
|
server.registerTool({
|
|
161
|
-
name: "my-
|
|
174
|
+
name: "my-view",
|
|
162
175
|
description: "Test tool",
|
|
163
176
|
view: {
|
|
164
|
-
component: "my-
|
|
165
|
-
description: "Test
|
|
177
|
+
component: "my-view",
|
|
178
|
+
description: "Test view",
|
|
166
179
|
prefersBorder: true,
|
|
167
180
|
},
|
|
168
181
|
}, vi.fn());
|
|
@@ -172,11 +185,11 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
172
185
|
const host = "localhost:3000";
|
|
173
186
|
const serverUrl = `http://${host}`;
|
|
174
187
|
const hmrUrl = `ws://${host}`;
|
|
175
|
-
const appsSdkResult = await appsSdkCallback(new URL("ui://
|
|
188
|
+
const appsSdkResult = await appsSdkCallback(new URL("ui://views/apps-sdk/my-view.html"), createMockExtra(host));
|
|
176
189
|
expect(appsSdkResult).toEqual({
|
|
177
190
|
contents: [
|
|
178
191
|
{
|
|
179
|
-
uri: "ui://
|
|
192
|
+
uri: "ui://views/apps-sdk/my-view.html",
|
|
180
193
|
mimeType: "text/html+skybridge",
|
|
181
194
|
text: expect.stringContaining('<div id="root"></div>'),
|
|
182
195
|
_meta: {
|
|
@@ -185,7 +198,7 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
185
198
|
connect_domains: [serverUrl, hmrUrl],
|
|
186
199
|
},
|
|
187
200
|
"openai/widgetDomain": serverUrl,
|
|
188
|
-
"openai/widgetDescription": "Test
|
|
201
|
+
"openai/widgetDescription": "Test view",
|
|
189
202
|
"openai/widgetPrefersBorder": true,
|
|
190
203
|
},
|
|
191
204
|
},
|
|
@@ -195,11 +208,11 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
195
208
|
const extAppsResourceCallback = mockRegisterResource.mock
|
|
196
209
|
.calls[1]?.[3];
|
|
197
210
|
expect(extAppsResourceCallback).toBeDefined();
|
|
198
|
-
const extAppsResult = await extAppsResourceCallback(new URL("ui://
|
|
211
|
+
const extAppsResult = await extAppsResourceCallback(new URL("ui://views/ext-apps/my-view.html"), createMockExtra(host));
|
|
199
212
|
expect(extAppsResult).toEqual({
|
|
200
213
|
contents: [
|
|
201
214
|
{
|
|
202
|
-
uri: "ui://
|
|
215
|
+
uri: "ui://views/ext-apps/my-view.html",
|
|
203
216
|
mimeType: "text/html;profile=mcp-app",
|
|
204
217
|
text: expect.stringContaining('<div id="root"></div>'),
|
|
205
218
|
_meta: {
|
|
@@ -210,7 +223,7 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
210
223
|
baseUriDomains: [serverUrl],
|
|
211
224
|
},
|
|
212
225
|
domain: serverUrl,
|
|
213
|
-
description: "Test
|
|
226
|
+
description: "Test view",
|
|
214
227
|
prefersBorder: true,
|
|
215
228
|
},
|
|
216
229
|
},
|
|
@@ -221,25 +234,25 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
221
234
|
});
|
|
222
235
|
it("should register tool with ui.resourceUri metadata", async () => {
|
|
223
236
|
server.registerTool({
|
|
224
|
-
name: "my-
|
|
237
|
+
name: "my-view",
|
|
225
238
|
description: "Test tool",
|
|
226
|
-
view: { component: "my-
|
|
239
|
+
view: { component: "my-view", description: "Test view" },
|
|
227
240
|
}, vi.fn());
|
|
228
241
|
expect(mockRegisterTool).toHaveBeenCalledTimes(1);
|
|
229
242
|
const toolCallArgs = mockRegisterTool.mock.calls[0];
|
|
230
243
|
const toolConfig = toolCallArgs?.[1];
|
|
231
244
|
expect(toolConfig._meta).toHaveProperty("ui");
|
|
232
245
|
expect(toolConfig._meta?.ui).toEqual({
|
|
233
|
-
resourceUri: "ui://
|
|
246
|
+
resourceUri: "ui://views/ext-apps/my-view.html",
|
|
234
247
|
});
|
|
235
248
|
});
|
|
236
249
|
it("should register tool with openai/outputTemplate when apps-sdk only", async () => {
|
|
237
250
|
server.registerTool({
|
|
238
|
-
name: "my-
|
|
251
|
+
name: "my-view",
|
|
239
252
|
description: "Test tool",
|
|
240
253
|
view: {
|
|
241
|
-
component: "my-
|
|
242
|
-
description: "Test
|
|
254
|
+
component: "my-view",
|
|
255
|
+
description: "Test view",
|
|
243
256
|
hosts: ["apps-sdk"],
|
|
244
257
|
},
|
|
245
258
|
}, vi.fn());
|
|
@@ -247,15 +260,71 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
247
260
|
const toolCallArgs = mockRegisterTool.mock.calls[0];
|
|
248
261
|
const toolConfig = toolCallArgs?.[1];
|
|
249
262
|
expect(toolConfig._meta).not.toHaveProperty("ui");
|
|
250
|
-
expect(toolConfig._meta?.["openai/outputTemplate"]).toBe("ui://
|
|
263
|
+
expect(toolConfig._meta?.["openai/outputTemplate"]).toBe("ui://views/apps-sdk/my-view.html");
|
|
264
|
+
});
|
|
265
|
+
it("should not version view URIs in development", () => {
|
|
266
|
+
server.registerTool({
|
|
267
|
+
name: "my-view",
|
|
268
|
+
description: "Test tool",
|
|
269
|
+
view: { component: "my-view", description: "Test view" },
|
|
270
|
+
}, vi.fn());
|
|
271
|
+
const toolConfig = mockRegisterTool.mock.calls[0]?.[1];
|
|
272
|
+
expect(toolConfig._meta?.["openai/outputTemplate"]).toBe("ui://views/apps-sdk/my-view.html");
|
|
273
|
+
expect(toolConfig._meta?.ui?.resourceUri).toBe("ui://views/ext-apps/my-view.html");
|
|
274
|
+
// The URI registered with the resource handler must match the URI in
|
|
275
|
+
// outputTemplate exactly so the SDK can resolve `resources/read` requests.
|
|
276
|
+
expect(mockRegisterResource.mock.calls[0]?.[1]).toBe("ui://views/apps-sdk/my-view.html");
|
|
277
|
+
expect(mockRegisterResource.mock.calls[1]?.[1]).toBe("ui://views/ext-apps/my-view.html");
|
|
278
|
+
});
|
|
279
|
+
it("should append a stable content hash to view URIs in production", () => {
|
|
280
|
+
setTestEnv({ NODE_ENV: "production" });
|
|
281
|
+
server.registerTool({
|
|
282
|
+
name: "my-view",
|
|
283
|
+
description: "Test tool",
|
|
284
|
+
view: { component: "my-view", description: "Test view" },
|
|
285
|
+
}, vi.fn());
|
|
286
|
+
const expected = expectedVersionParam("assets/my-view-abc123.js", "style.css");
|
|
287
|
+
const toolConfig = mockRegisterTool.mock.calls[0]?.[1];
|
|
288
|
+
expect(toolConfig._meta?.["openai/outputTemplate"]).toBe(`ui://views/apps-sdk/my-view.html${expected}`);
|
|
289
|
+
expect(toolConfig._meta?.ui?.resourceUri).toBe(`ui://views/ext-apps/my-view.html${expected}`);
|
|
290
|
+
expect(mockRegisterResource.mock.calls[0]?.[1]).toBe(`ui://views/apps-sdk/my-view.html${expected}`);
|
|
291
|
+
expect(mockRegisterResource.mock.calls[1]?.[1]).toBe(`ui://views/ext-apps/my-view.html${expected}`);
|
|
292
|
+
});
|
|
293
|
+
it("should produce different version params for views with different bundles", () => {
|
|
294
|
+
setTestEnv({ NODE_ENV: "production" });
|
|
295
|
+
server.registerTool({
|
|
296
|
+
name: "my-view",
|
|
297
|
+
description: "First tool",
|
|
298
|
+
view: { component: "my-view" },
|
|
299
|
+
}, vi.fn());
|
|
300
|
+
server.registerTool({
|
|
301
|
+
name: "folder-view",
|
|
302
|
+
description: "Second tool",
|
|
303
|
+
view: { component: "folder-view" },
|
|
304
|
+
}, vi.fn());
|
|
305
|
+
const myviewTemplate = (mockRegisterTool.mock.calls[0]?.[1])._meta?.["openai/outputTemplate"];
|
|
306
|
+
const folderviewTemplate = (mockRegisterTool.mock.calls[1]?.[1])._meta?.["openai/outputTemplate"];
|
|
307
|
+
expect(myviewTemplate).not.toEqual(folderviewTemplate);
|
|
308
|
+
expect(myviewTemplate).toMatch(/\?v=[0-9a-f]{8}$/);
|
|
309
|
+
expect(folderviewTemplate).toMatch(/\?v=[0-9a-f]{8}$/);
|
|
310
|
+
});
|
|
311
|
+
it("should fall back to bare URI in production when manifest is missing", () => {
|
|
312
|
+
setTestEnv({ NODE_ENV: "production" });
|
|
313
|
+
server.registerTool({
|
|
314
|
+
name: "unknown-view",
|
|
315
|
+
description: "Test tool",
|
|
316
|
+
view: { component: "unknown-view" },
|
|
317
|
+
}, vi.fn());
|
|
318
|
+
const toolConfig = mockRegisterTool.mock.calls[0]?.[1];
|
|
319
|
+
expect(toolConfig._meta?.["openai/outputTemplate"]).toBe("ui://views/apps-sdk/unknown-view.html");
|
|
251
320
|
});
|
|
252
321
|
it("should register tool with ui.resourceUri only when mcp-app only", async () => {
|
|
253
322
|
server.registerTool({
|
|
254
|
-
name: "my-
|
|
323
|
+
name: "my-view",
|
|
255
324
|
description: "Test tool",
|
|
256
325
|
view: {
|
|
257
|
-
component: "my-
|
|
258
|
-
description: "Test
|
|
326
|
+
component: "my-view",
|
|
327
|
+
description: "Test view",
|
|
259
328
|
hosts: ["mcp-app"],
|
|
260
329
|
},
|
|
261
330
|
}, vi.fn());
|
|
@@ -264,7 +333,7 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
264
333
|
const toolConfig = toolCallArgs?.[1];
|
|
265
334
|
expect(toolConfig._meta).toHaveProperty("ui");
|
|
266
335
|
expect(toolConfig._meta?.ui).toEqual({
|
|
267
|
-
resourceUri: "ui://
|
|
336
|
+
resourceUri: "ui://views/ext-apps/my-view.html",
|
|
268
337
|
});
|
|
269
338
|
expect(toolConfig._meta?.["openai/outputTemplate"]).toBeUndefined();
|
|
270
339
|
});
|
|
@@ -274,9 +343,9 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
274
343
|
structuredContent: { data: "test" },
|
|
275
344
|
});
|
|
276
345
|
server.registerTool({
|
|
277
|
-
name: "my-
|
|
346
|
+
name: "my-view",
|
|
278
347
|
description: "Test tool",
|
|
279
|
-
view: { component: "my-
|
|
348
|
+
view: { component: "my-view", description: "Test view" },
|
|
280
349
|
}, mockToolCallback);
|
|
281
350
|
const wrappedCallback = mockRegisterTool.mock.calls[0]?.[2];
|
|
282
351
|
expect(wrappedCallback).toBeDefined();
|
|
@@ -293,9 +362,9 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
293
362
|
_meta: { requestId: "req-123", cached: true },
|
|
294
363
|
});
|
|
295
364
|
server.registerTool({
|
|
296
|
-
name: "my-
|
|
365
|
+
name: "my-view",
|
|
297
366
|
description: "Test tool",
|
|
298
|
-
view: { component: "my-
|
|
367
|
+
view: { component: "my-view", description: "Test view" },
|
|
299
368
|
}, mockToolCallback);
|
|
300
369
|
const wrappedCallback = mockRegisterTool.mock.calls[0]?.[2];
|
|
301
370
|
const result = await wrappedCallback({}, {});
|
|
@@ -309,16 +378,16 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
309
378
|
structuredContent: {},
|
|
310
379
|
});
|
|
311
380
|
server.registerTool({
|
|
312
|
-
name: "my-
|
|
381
|
+
name: "my-view",
|
|
313
382
|
description: "Test tool",
|
|
314
|
-
view: { component: "my-
|
|
383
|
+
view: { component: "my-view", description: "Test view" },
|
|
315
384
|
}, mockToolCallback);
|
|
316
385
|
const wrappedCallback = mockRegisterTool.mock.calls[0]?.[2];
|
|
317
386
|
const result1 = await wrappedCallback({}, {});
|
|
318
387
|
const result2 = await wrappedCallback({}, {});
|
|
319
388
|
expect(result1._meta?.viewUUID).not.toBe(result2._meta?.viewUUID);
|
|
320
389
|
});
|
|
321
|
-
it("should enforce one-tool-per-
|
|
390
|
+
it("should enforce one-tool-per-view constraint", () => {
|
|
322
391
|
server.registerTool({
|
|
323
392
|
name: "shake",
|
|
324
393
|
description: "First tool",
|
|
@@ -330,7 +399,7 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
330
399
|
description: "Second tool",
|
|
331
400
|
view: { component: "magic-8-ball" },
|
|
332
401
|
}, vi.fn());
|
|
333
|
-
}).toThrow('skybridge:
|
|
402
|
+
}).toThrow('skybridge: view "magic-8-ball" is already used by tool "shake"');
|
|
334
403
|
});
|
|
335
404
|
it("should normalize string content to ContentBlock array", async () => {
|
|
336
405
|
const mockToolCallback = vi.fn().mockResolvedValue({
|
|
@@ -381,7 +450,7 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
381
450
|
it("should register tool without view (no resource registration)", () => {
|
|
382
451
|
server.registerTool({
|
|
383
452
|
name: "plain-tool",
|
|
384
|
-
description: "No
|
|
453
|
+
description: "No view",
|
|
385
454
|
}, vi.fn());
|
|
386
455
|
expect(mockRegisterResource).not.toHaveBeenCalled();
|
|
387
456
|
expect(mockRegisterTool).toHaveBeenCalledTimes(1);
|
|
@@ -392,7 +461,7 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
392
461
|
description: "Test tool",
|
|
393
462
|
view: {
|
|
394
463
|
component: "csp-tool",
|
|
395
|
-
description: "Test
|
|
464
|
+
description: "Test view",
|
|
396
465
|
csp: {
|
|
397
466
|
connectDomains: ["https://api.example.com"],
|
|
398
467
|
resourceDomains: ["https://cdn.example.com"],
|
|
@@ -404,7 +473,7 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
404
473
|
const host = "localhost:3000";
|
|
405
474
|
const serverUrl = `http://${host}`;
|
|
406
475
|
const hmrUrl = `ws://${host}`;
|
|
407
|
-
const result = await appsSdkCallback(new URL("ui://
|
|
476
|
+
const result = await appsSdkCallback(new URL("ui://views/apps-sdk/csp-tool.html"), createMockExtra(host));
|
|
408
477
|
const meta = result.contents[0]?._meta;
|
|
409
478
|
expect(meta["openai/widgetCSP"]).toEqual({
|
|
410
479
|
resource_domains: [serverUrl, "https://cdn.example.com"],
|
|
@@ -417,7 +486,7 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
417
486
|
description: "Test tool",
|
|
418
487
|
view: {
|
|
419
488
|
component: "override-tool",
|
|
420
|
-
description: "Test
|
|
489
|
+
description: "Test view",
|
|
421
490
|
csp: { connectDomains: ["https://api.x.com"] },
|
|
422
491
|
_meta: {
|
|
423
492
|
"openai/widgetCSP": {
|
|
@@ -428,7 +497,7 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
428
497
|
}, vi.fn());
|
|
429
498
|
const appsSdkCallback = mockRegisterResource.mock
|
|
430
499
|
.calls[0]?.[3];
|
|
431
|
-
const result = await appsSdkCallback(new URL("ui://
|
|
500
|
+
const result = await appsSdkCallback(new URL("ui://views/apps-sdk/override-tool.html"), createMockExtra("localhost:3000"));
|
|
432
501
|
const meta = result.contents[0]?._meta;
|
|
433
502
|
expect(meta["openai/widgetCSP"]).toEqual({
|
|
434
503
|
connect_domains: ["https://api.y.com"],
|
|
@@ -451,4 +520,4 @@ describe("McpServer.registerTool (unified API)", () => {
|
|
|
451
520
|
expect(toolConfig._meta?.["acme.com/category"]).toBe("utility");
|
|
452
521
|
});
|
|
453
522
|
});
|
|
454
|
-
//# sourceMappingURL=
|
|
523
|
+
//# sourceMappingURL=view.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view.test.js","sourceRoot":"","sources":["../../src/test/view.test.ts"],"names":[],"mappings":"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","sourcesContent":["import crypto from \"node:crypto\";\nimport type { RequestHandlerExtra } from \"@modelcontextprotocol/sdk/shared/protocol.js\";\nimport type {\n ServerNotification,\n ServerRequest,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport {\n afterEach,\n beforeEach,\n describe,\n expect,\n it,\n type MockInstance,\n vi,\n} from \"vitest\";\nimport type { McpServer, ViewName } from \"../server/server.js\";\nimport {\n createMockExtra,\n createMockMcpServer,\n resetTestEnv,\n setTestEnv,\n} from \"./utils.js\";\n\nconst mockManifest = {\n \"skybridge:view:my-view\": {\n file: \"assets/my-view-abc123.js\",\n name: \"my-view\",\n isEntry: true,\n },\n \"skybridge:view:folder-view\": {\n file: \"assets/folder-view-def456.js\",\n name: \"folder-view\",\n isEntry: true,\n },\n \"style.css\": { file: \"style.css\" },\n};\n\n// Mirrors `McpServer.computeViewVersionParam`. Tests recompute the expected\n// hash from the mocked manifest so they don't hardcode digest output.\nfunction expectedVersionParam(viewFile: string, styleFile: string): string {\n const hash = crypto\n .createHash(\"sha256\")\n .update(viewFile)\n .update(\"\\0\")\n .update(styleFile)\n .digest(\"hex\")\n .slice(0, 8);\n return `?v=${hash}`;\n}\n\nconst actual = vi.hoisted(() => require(\"node:fs\"));\n\nvi.mock(\"node:fs\", () => {\n const readFileSyncImpl = (\n path: Parameters<typeof actual.readFileSync>[0],\n ...args: unknown[]\n ): ReturnType<typeof actual.readFileSync> => {\n if (typeof path === \"string\" && path.includes(\"manifest.json\")) {\n return JSON.stringify(mockManifest) as ReturnType<\n typeof actual.readFileSync\n >;\n }\n return actual.readFileSync(path, ...args);\n };\n const readFileSync = vi.fn(readFileSyncImpl) as typeof actual.readFileSync;\n\n return {\n readFileSync,\n default: {\n readFileSync,\n },\n };\n});\n\ndescribe(\"McpServer.registerTool (unified API)\", () => {\n let server: McpServer;\n let mockRegisterResource: MockInstance<McpServer[\"registerResource\"]>;\n let mockRegisterTool: MockInstance;\n\n beforeEach(() => {\n ({ server, mockRegisterResource, mockRegisterTool } =\n createMockMcpServer());\n });\n\n afterEach(() => {\n vi.clearAllMocks();\n resetTestEnv();\n });\n\n it(\"should generate correct HTML for development mode\", async () => {\n setTestEnv({ NODE_ENV: \"development\" });\n\n server.registerTool(\n {\n name: \"my-view\",\n description: \"Test tool\",\n view: {\n component: \"my-view\" as ViewName,\n description: \"Test view\",\n },\n },\n vi.fn(),\n );\n\n const appsSdkResourceCallback = mockRegisterResource.mock\n .calls[0]?.[3] as unknown as (\n uri: URL,\n extra: RequestHandlerExtra<ServerRequest, ServerNotification>,\n ) => Promise<{\n contents: Array<{ uri: URL | string; mimeType: string; text?: string }>;\n }>;\n expect(appsSdkResourceCallback).toBeDefined();\n\n const host = \"localhost:3000\";\n const serverUrl = `http://${host}`;\n const hmrUrl = `ws://${host}`;\n const mockExtra = createMockExtra(host) as unknown as RequestHandlerExtra<\n ServerRequest,\n ServerNotification\n >;\n const result = await appsSdkResourceCallback(\n new URL(\"ui://views/apps-sdk/my-view.html\"),\n mockExtra,\n );\n\n expect(mockRegisterTool).toHaveBeenCalled();\n expect(result).toEqual({\n contents: [\n {\n uri: \"ui://views/apps-sdk/my-view.html\",\n mimeType: \"text/html+skybridge\",\n text: expect.stringContaining('<div id=\"root\"></div>'),\n _meta: {\n \"openai/widgetCSP\": {\n resource_domains: [serverUrl],\n connect_domains: [serverUrl, hmrUrl],\n },\n \"openai/widgetDomain\": serverUrl,\n \"openai/widgetDescription\": \"Test view\",\n },\n },\n ],\n });\n\n expect(result.contents[0]?.text).toContain(\n `${serverUrl}/assets/@react-refresh`,\n );\n expect(result.contents[0]?.text).toContain(`${serverUrl}/@vite/client`);\n expect(result.contents[0]?.text).toContain(\n `${serverUrl}/_skybridge/view/my-view`,\n );\n });\n\n it(\"should generate correct HTML for production mode\", async () => {\n setTestEnv({ NODE_ENV: \"production\" });\n\n server.registerTool(\n {\n name: \"my-view\",\n description: \"Test tool\",\n view: {\n component: \"my-view\" as ViewName,\n description: \"Test view\",\n },\n },\n vi.fn(),\n );\n\n const appsSdkResourceCallback = mockRegisterResource.mock\n .calls[0]?.[3] as unknown as (\n uri: URL,\n extra: RequestHandlerExtra<ServerRequest, ServerNotification>,\n ) => Promise<{\n contents: Array<{ uri: URL | string; mimeType: string; text?: string }>;\n }>;\n expect(appsSdkResourceCallback).toBeDefined();\n\n const host = \"myapp.com\";\n const serverUrl = `https://${host}`;\n const mockExtra = createMockExtra(host) as unknown as RequestHandlerExtra<\n ServerRequest,\n ServerNotification\n >;\n const versionedUri = `ui://views/apps-sdk/my-view.html${expectedVersionParam(\"assets/my-view-abc123.js\", \"style.css\")}`;\n const result = await appsSdkResourceCallback(\n new URL(versionedUri),\n mockExtra,\n );\n\n expect(result).toEqual({\n contents: [\n {\n uri: versionedUri,\n mimeType: \"text/html+skybridge\",\n text: expect.stringContaining('<div id=\"root\"></div>'),\n _meta: {\n \"openai/widgetCSP\": {\n resource_domains: [serverUrl],\n connect_domains: [serverUrl],\n },\n \"openai/widgetDomain\": serverUrl,\n \"openai/widgetDescription\": \"Test view\",\n },\n },\n ],\n });\n\n expect(result.contents[0]?.text).not.toContain(\n `${serverUrl}/assets/@react-refresh`,\n );\n expect(result.contents[0]?.text).not.toContain(`${serverUrl}@vite/client`);\n expect(result.contents[0]?.text).toContain(\n `${serverUrl}/assets/assets/my-view-abc123.js`,\n );\n expect(result.contents[0]?.text).toContain(`${serverUrl}/assets/style.css`);\n });\n\n it(\"should prefer x-alpic-forwarded-url when hashing Claude view domains\", async () => {\n setTestEnv({ NODE_ENV: \"production\" });\n\n server.registerTool(\n {\n name: \"my-view\",\n description: \"Test tool\",\n view: { component: \"my-view\" as ViewName, description: \"Test view\" },\n },\n vi.fn(),\n );\n\n const extAppsResourceCallback = mockRegisterResource.mock\n .calls[1]?.[3] as unknown as (\n uri: URL,\n extra: RequestHandlerExtra<ServerRequest, ServerNotification>,\n ) => Promise<{\n contents: Array<{\n uri: URL | string;\n mimeType: string;\n text?: string;\n _meta?: Record<string, unknown>;\n }>;\n }>;\n expect(extAppsResourceCallback).toBeDefined();\n\n const forwardedUrl =\n \"https://everything-3a2c1264.staging.alpic.live/mcp?foo=bar\";\n const expectedDomain = `${crypto\n .createHash(\"sha256\")\n .update(forwardedUrl)\n .digest(\"hex\")\n .slice(0, 32)}.claudemcpcontent.com`;\n\n const result = await extAppsResourceCallback(\n new URL(\n `ui://views/ext-apps/my-view.html${expectedVersionParam(\"assets/my-view-abc123.js\", \"style.css\")}`,\n ),\n createMockExtra(\"localhost:3000\", {\n headers: {\n \"user-agent\": \"Claude-User\",\n \"x-alpic-forwarded-url\": forwardedUrl,\n },\n url: \"http://localhost:3000/mcp\",\n }) as unknown as RequestHandlerExtra<ServerRequest, ServerNotification>,\n );\n\n expect(result.contents[0]?._meta).toEqual({\n ui: {\n csp: {\n resourceDomains: [\"http://localhost:3000\"],\n connectDomains: [\"http://localhost:3000\"],\n baseUriDomains: [\"http://localhost:3000\"],\n },\n description: \"Test view\",\n domain: expectedDomain,\n },\n });\n });\n\n it(\"should register resources with correct hostType for both apps-sdk and ext-apps\", async () => {\n server.registerTool(\n {\n name: \"my-view\",\n description: \"Test tool\",\n view: {\n component: \"my-view\" as ViewName,\n description: \"Test view\",\n prefersBorder: true,\n },\n },\n vi.fn(),\n );\n\n expect(mockRegisterResource).toHaveBeenCalledTimes(2);\n\n const appsSdkCallback = mockRegisterResource.mock\n .calls[0]?.[3] as unknown as (\n uri: URL,\n extra: RequestHandlerExtra<ServerRequest, ServerNotification>,\n ) => Promise<{\n contents: Array<{ uri: URL | string; mimeType: string; text?: string }>;\n }>;\n const host = \"localhost:3000\";\n const serverUrl = `http://${host}`;\n const hmrUrl = `ws://${host}`;\n\n const appsSdkResult = await appsSdkCallback(\n new URL(\"ui://views/apps-sdk/my-view.html\"),\n createMockExtra(host) as unknown as RequestHandlerExtra<\n ServerRequest,\n ServerNotification\n >,\n );\n\n expect(appsSdkResult).toEqual({\n contents: [\n {\n uri: \"ui://views/apps-sdk/my-view.html\",\n mimeType: \"text/html+skybridge\",\n text: expect.stringContaining('<div id=\"root\"></div>'),\n _meta: {\n \"openai/widgetCSP\": {\n resource_domains: [serverUrl],\n connect_domains: [serverUrl, hmrUrl],\n },\n \"openai/widgetDomain\": serverUrl,\n \"openai/widgetDescription\": \"Test view\",\n \"openai/widgetPrefersBorder\": true,\n },\n },\n ],\n });\n expect(appsSdkResult.contents[0]?.text).toContain(\n 'window.skybridge = { hostType: \"apps-sdk\", serverUrl: \"http://localhost:3000\" };',\n );\n\n const extAppsResourceCallback = mockRegisterResource.mock\n .calls[1]?.[3] as unknown as (\n uri: URL,\n extra: RequestHandlerExtra<ServerRequest, ServerNotification>,\n ) => Promise<{\n contents: Array<{\n uri: URL | string;\n mimeType: string;\n text?: string;\n _meta?: Record<string, unknown>;\n }>;\n }>;\n expect(extAppsResourceCallback).toBeDefined();\n\n const extAppsResult = await extAppsResourceCallback(\n new URL(\"ui://views/ext-apps/my-view.html\"),\n createMockExtra(host) as unknown as RequestHandlerExtra<\n ServerRequest,\n ServerNotification\n >,\n );\n\n expect(extAppsResult).toEqual({\n contents: [\n {\n uri: \"ui://views/ext-apps/my-view.html\",\n mimeType: \"text/html;profile=mcp-app\",\n text: expect.stringContaining('<div id=\"root\"></div>'),\n _meta: {\n ui: {\n csp: {\n resourceDomains: [serverUrl],\n connectDomains: [serverUrl, hmrUrl],\n baseUriDomains: [serverUrl],\n },\n domain: serverUrl,\n description: \"Test view\",\n prefersBorder: true,\n },\n },\n },\n ],\n });\n expect(extAppsResult.contents[0]?.text).toContain(\n 'window.skybridge = { hostType: \"mcp-app\", serverUrl: \"http://localhost:3000\" };',\n );\n });\n\n it(\"should register tool with ui.resourceUri metadata\", async () => {\n server.registerTool(\n {\n name: \"my-view\",\n description: \"Test tool\",\n view: { component: \"my-view\" as ViewName, description: \"Test view\" },\n },\n vi.fn(),\n );\n\n expect(mockRegisterTool).toHaveBeenCalledTimes(1);\n\n const toolCallArgs = mockRegisterTool.mock.calls[0];\n const toolConfig = toolCallArgs?.[1] as { _meta?: Record<string, unknown> };\n\n expect(toolConfig._meta).toHaveProperty(\"ui\");\n expect(toolConfig._meta?.ui).toEqual({\n resourceUri: \"ui://views/ext-apps/my-view.html\",\n });\n });\n\n it(\"should register tool with openai/outputTemplate when apps-sdk only\", async () => {\n server.registerTool(\n {\n name: \"my-view\",\n description: \"Test tool\",\n view: {\n component: \"my-view\" as ViewName,\n description: \"Test view\",\n hosts: [\"apps-sdk\"],\n },\n },\n vi.fn(),\n );\n\n expect(mockRegisterTool).toHaveBeenCalledTimes(1);\n\n const toolCallArgs = mockRegisterTool.mock.calls[0];\n const toolConfig = toolCallArgs?.[1] as { _meta?: Record<string, unknown> };\n\n expect(toolConfig._meta).not.toHaveProperty(\"ui\");\n expect(toolConfig._meta?.[\"openai/outputTemplate\"]).toBe(\n \"ui://views/apps-sdk/my-view.html\",\n );\n });\n\n it(\"should not version view URIs in development\", () => {\n server.registerTool(\n {\n name: \"my-view\",\n description: \"Test tool\",\n view: { component: \"my-view\" as ViewName, description: \"Test view\" },\n },\n vi.fn(),\n );\n\n const toolConfig = mockRegisterTool.mock.calls[0]?.[1] as {\n _meta?: Record<string, unknown> & { ui?: { resourceUri?: string } };\n };\n\n expect(toolConfig._meta?.[\"openai/outputTemplate\"]).toBe(\n \"ui://views/apps-sdk/my-view.html\",\n );\n expect(toolConfig._meta?.ui?.resourceUri).toBe(\n \"ui://views/ext-apps/my-view.html\",\n );\n // The URI registered with the resource handler must match the URI in\n // outputTemplate exactly so the SDK can resolve `resources/read` requests.\n expect(mockRegisterResource.mock.calls[0]?.[1]).toBe(\n \"ui://views/apps-sdk/my-view.html\",\n );\n expect(mockRegisterResource.mock.calls[1]?.[1]).toBe(\n \"ui://views/ext-apps/my-view.html\",\n );\n });\n\n it(\"should append a stable content hash to view URIs in production\", () => {\n setTestEnv({ NODE_ENV: \"production\" });\n\n server.registerTool(\n {\n name: \"my-view\",\n description: \"Test tool\",\n view: { component: \"my-view\" as ViewName, description: \"Test view\" },\n },\n vi.fn(),\n );\n\n const expected = expectedVersionParam(\n \"assets/my-view-abc123.js\",\n \"style.css\",\n );\n const toolConfig = mockRegisterTool.mock.calls[0]?.[1] as {\n _meta?: Record<string, unknown> & { ui?: { resourceUri?: string } };\n };\n\n expect(toolConfig._meta?.[\"openai/outputTemplate\"]).toBe(\n `ui://views/apps-sdk/my-view.html${expected}`,\n );\n expect(toolConfig._meta?.ui?.resourceUri).toBe(\n `ui://views/ext-apps/my-view.html${expected}`,\n );\n expect(mockRegisterResource.mock.calls[0]?.[1]).toBe(\n `ui://views/apps-sdk/my-view.html${expected}`,\n );\n expect(mockRegisterResource.mock.calls[1]?.[1]).toBe(\n `ui://views/ext-apps/my-view.html${expected}`,\n );\n });\n\n it(\"should produce different version params for views with different bundles\", () => {\n setTestEnv({ NODE_ENV: \"production\" });\n\n server.registerTool(\n {\n name: \"my-view\",\n description: \"First tool\",\n view: { component: \"my-view\" as ViewName },\n },\n vi.fn(),\n );\n server.registerTool(\n {\n name: \"folder-view\",\n description: \"Second tool\",\n view: { component: \"folder-view\" as ViewName },\n },\n vi.fn(),\n );\n\n const myviewTemplate = (\n mockRegisterTool.mock.calls[0]?.[1] as { _meta?: Record<string, unknown> }\n )._meta?.[\"openai/outputTemplate\"];\n const folderviewTemplate = (\n mockRegisterTool.mock.calls[1]?.[1] as { _meta?: Record<string, unknown> }\n )._meta?.[\"openai/outputTemplate\"];\n\n expect(myviewTemplate).not.toEqual(folderviewTemplate);\n expect(myviewTemplate).toMatch(/\\?v=[0-9a-f]{8}$/);\n expect(folderviewTemplate).toMatch(/\\?v=[0-9a-f]{8}$/);\n });\n\n it(\"should fall back to bare URI in production when manifest is missing\", () => {\n setTestEnv({ NODE_ENV: \"production\" });\n\n server.registerTool(\n {\n name: \"unknown-view\",\n description: \"Test tool\",\n view: { component: \"unknown-view\" as ViewName },\n },\n vi.fn(),\n );\n\n const toolConfig = mockRegisterTool.mock.calls[0]?.[1] as {\n _meta?: Record<string, unknown>;\n };\n expect(toolConfig._meta?.[\"openai/outputTemplate\"]).toBe(\n \"ui://views/apps-sdk/unknown-view.html\",\n );\n });\n\n it(\"should register tool with ui.resourceUri only when mcp-app only\", async () => {\n server.registerTool(\n {\n name: \"my-view\",\n description: \"Test tool\",\n view: {\n component: \"my-view\" as ViewName,\n description: \"Test view\",\n hosts: [\"mcp-app\"],\n },\n },\n vi.fn(),\n );\n\n expect(mockRegisterTool).toHaveBeenCalledTimes(1);\n\n const toolCallArgs = mockRegisterTool.mock.calls[0];\n const toolConfig = toolCallArgs?.[1] as { _meta?: Record<string, unknown> };\n\n expect(toolConfig._meta).toHaveProperty(\"ui\");\n expect(toolConfig._meta?.ui).toEqual({\n resourceUri: \"ui://views/ext-apps/my-view.html\",\n });\n expect(toolConfig._meta?.[\"openai/outputTemplate\"]).toBeUndefined();\n });\n\n it(\"should inject viewUUID into _meta of tool callback results\", async () => {\n const mockToolCallback = vi.fn().mockResolvedValue({\n content: [{ type: \"text\", text: \"result\" }],\n structuredContent: { data: \"test\" },\n });\n\n server.registerTool(\n {\n name: \"my-view\",\n description: \"Test tool\",\n view: { component: \"my-view\" as ViewName, description: \"Test view\" },\n },\n mockToolCallback,\n );\n\n const wrappedCallback = mockRegisterTool.mock.calls[0]?.[2] as (\n ...args: unknown[]\n ) => Promise<{ _meta?: Record<string, unknown> }>;\n expect(wrappedCallback).toBeDefined();\n\n const result = await wrappedCallback({}, {});\n\n expect(result._meta).toBeDefined();\n expect(result._meta?.viewUUID).toBeDefined();\n expect(typeof result._meta?.viewUUID).toBe(\"string\");\n expect(result._meta?.viewUUID).toMatch(\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,\n );\n });\n\n it(\"should preserve existing _meta when injecting viewUUID\", async () => {\n const mockToolCallback = vi.fn().mockResolvedValue({\n content: [{ type: \"text\", text: \"result\" }],\n structuredContent: { data: \"test\" },\n _meta: { requestId: \"req-123\", cached: true },\n });\n\n server.registerTool(\n {\n name: \"my-view\",\n description: \"Test tool\",\n view: { component: \"my-view\" as ViewName, description: \"Test view\" },\n },\n mockToolCallback,\n );\n\n const wrappedCallback = mockRegisterTool.mock.calls[0]?.[2] as (\n ...args: unknown[]\n ) => Promise<{ _meta?: Record<string, unknown> }>;\n const result = await wrappedCallback({}, {});\n\n expect(result._meta?.requestId).toBe(\"req-123\");\n expect(result._meta?.cached).toBe(true);\n expect(result._meta?.viewUUID).toBeDefined();\n });\n\n it(\"should generate unique viewUUIDs across calls\", async () => {\n const mockToolCallback = vi.fn().mockResolvedValue({\n content: [{ type: \"text\", text: \"result\" }],\n structuredContent: {},\n });\n\n server.registerTool(\n {\n name: \"my-view\",\n description: \"Test tool\",\n view: { component: \"my-view\" as ViewName, description: \"Test view\" },\n },\n mockToolCallback,\n );\n\n const wrappedCallback = mockRegisterTool.mock.calls[0]?.[2] as (\n ...args: unknown[]\n ) => Promise<{ _meta?: Record<string, unknown> }>;\n const result1 = await wrappedCallback({}, {});\n const result2 = await wrappedCallback({}, {});\n\n expect(result1._meta?.viewUUID).not.toBe(result2._meta?.viewUUID);\n });\n\n it(\"should enforce one-tool-per-view constraint\", () => {\n server.registerTool(\n {\n name: \"shake\",\n description: \"First tool\",\n view: { component: \"magic-8-ball\" as ViewName },\n },\n vi.fn(),\n );\n\n expect(() => {\n server.registerTool(\n {\n name: \"shake-v2\",\n description: \"Second tool\",\n view: { component: \"magic-8-ball\" as ViewName },\n },\n vi.fn(),\n );\n }).toThrow(\n 'skybridge: view \"magic-8-ball\" is already used by tool \"shake\"',\n );\n });\n\n it(\"should normalize string content to ContentBlock array\", async () => {\n const mockToolCallback = vi.fn().mockResolvedValue({\n content: \"Hello world\",\n structuredContent: {},\n });\n\n server.registerTool(\n {\n name: \"string-content\",\n description: \"Test tool\",\n view: { component: \"string-content\" as ViewName },\n },\n mockToolCallback,\n );\n\n const wrappedCallback = mockRegisterTool.mock.calls[0]?.[2] as (\n ...args: unknown[]\n ) => Promise<{ content: unknown[] }>;\n const result = await wrappedCallback({}, {});\n\n expect(result.content).toEqual([{ type: \"text\", text: \"Hello world\" }]);\n });\n\n it(\"should normalize single ContentBlock to array\", async () => {\n const mockToolCallback = vi.fn().mockResolvedValue({\n content: { type: \"text\", text: \"Single block\" },\n structuredContent: {},\n });\n\n server.registerTool(\n {\n name: \"single-block\",\n description: \"Test tool\",\n view: { component: \"single-block\" as ViewName },\n },\n mockToolCallback,\n );\n\n const wrappedCallback = mockRegisterTool.mock.calls[0]?.[2] as (\n ...args: unknown[]\n ) => Promise<{ content: unknown[] }>;\n const result = await wrappedCallback({}, {});\n\n expect(result.content).toEqual([{ type: \"text\", text: \"Single block\" }]);\n });\n\n it(\"should pass through ContentBlock array unchanged\", async () => {\n const blocks = [\n { type: \"text\", text: \"A\" },\n { type: \"text\", text: \"B\" },\n ];\n const mockToolCallback = vi.fn().mockResolvedValue({\n content: blocks,\n structuredContent: {},\n });\n\n server.registerTool(\n {\n name: \"array-content\",\n description: \"Test tool\",\n view: { component: \"array-content\" as ViewName },\n },\n mockToolCallback,\n );\n\n const wrappedCallback = mockRegisterTool.mock.calls[0]?.[2] as (\n ...args: unknown[]\n ) => Promise<{ content: unknown[] }>;\n const result = await wrappedCallback({}, {});\n\n expect(result.content).toEqual(blocks);\n });\n\n it(\"should register tool without view (no resource registration)\", () => {\n server.registerTool(\n {\n name: \"plain-tool\",\n description: \"No view\",\n },\n vi.fn(),\n );\n\n expect(mockRegisterResource).not.toHaveBeenCalled();\n expect(mockRegisterTool).toHaveBeenCalledTimes(1);\n });\n\n it(\"should apply view.csp fields to resource _meta\", async () => {\n server.registerTool(\n {\n name: \"csp-tool\",\n description: \"Test tool\",\n view: {\n component: \"csp-tool\" as ViewName,\n description: \"Test view\",\n csp: {\n connectDomains: [\"https://api.example.com\"],\n resourceDomains: [\"https://cdn.example.com\"],\n },\n },\n },\n vi.fn(),\n );\n\n const appsSdkCallback = mockRegisterResource.mock\n .calls[0]?.[3] as unknown as (\n uri: URL,\n extra: RequestHandlerExtra<ServerRequest, ServerNotification>,\n ) => Promise<{\n contents: Array<{\n uri: URL | string;\n mimeType: string;\n _meta?: Record<string, unknown>;\n }>;\n }>;\n\n const host = \"localhost:3000\";\n const serverUrl = `http://${host}`;\n const hmrUrl = `ws://${host}`;\n const result = await appsSdkCallback(\n new URL(\"ui://views/apps-sdk/csp-tool.html\"),\n createMockExtra(host) as unknown as RequestHandlerExtra<\n ServerRequest,\n ServerNotification\n >,\n );\n\n const meta = result.contents[0]?._meta as Record<string, unknown>;\n expect(meta[\"openai/widgetCSP\"]).toEqual({\n resource_domains: [serverUrl, \"https://cdn.example.com\"],\n connect_domains: [serverUrl, hmrUrl, \"https://api.example.com\"],\n });\n });\n\n it(\"should let view._meta override framework-computed keys\", async () => {\n server.registerTool(\n {\n name: \"override-tool\",\n description: \"Test tool\",\n view: {\n component: \"override-tool\" as ViewName,\n description: \"Test view\",\n csp: { connectDomains: [\"https://api.x.com\"] },\n _meta: {\n \"openai/widgetCSP\": {\n connect_domains: [\"https://api.y.com\"],\n },\n },\n },\n },\n vi.fn(),\n );\n\n const appsSdkCallback = mockRegisterResource.mock\n .calls[0]?.[3] as unknown as (\n uri: URL,\n extra: RequestHandlerExtra<ServerRequest, ServerNotification>,\n ) => Promise<{\n contents: Array<{\n uri: URL | string;\n mimeType: string;\n _meta?: Record<string, unknown>;\n }>;\n }>;\n\n const result = await appsSdkCallback(\n new URL(\"ui://views/apps-sdk/override-tool.html\"),\n createMockExtra(\"localhost:3000\") as unknown as RequestHandlerExtra<\n ServerRequest,\n ServerNotification\n >,\n );\n\n const meta = result.contents[0]?._meta as Record<string, unknown>;\n expect(meta[\"openai/widgetCSP\"]).toEqual({\n connect_domains: [\"https://api.y.com\"],\n });\n });\n\n it(\"should pass user _meta keys through to tool config\", () => {\n server.registerTool(\n {\n name: \"meta-tool\",\n description: \"Test tool\",\n _meta: {\n \"openai/widgetAccessible\": true,\n \"openai/toolInvocation/invoking\": \"Loading...\",\n \"acme.com/category\": \"utility\",\n },\n },\n vi.fn(),\n );\n\n const toolCallArgs = mockRegisterTool.mock.calls[0];\n const toolConfig = toolCallArgs?.[1] as { _meta?: Record<string, unknown> };\n\n expect(toolConfig._meta?.[\"openai/widgetAccessible\"]).toBe(true);\n expect(toolConfig._meta?.[\"openai/toolInvocation/invoking\"]).toBe(\n \"Loading...\",\n );\n expect(toolConfig._meta?.[\"acme.com/category\"]).toBe(\"utility\");\n });\n});\n"]}
|
package/dist/version.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
const require = createRequire(import.meta.url);
|
|
3
|
-
const pkg = require("../package.json");
|
|
1
|
+
import pkg from "../package.json" with { type: "json" };
|
|
4
2
|
export const VERSION = pkg.version;
|
|
5
3
|
//# sourceMappingURL=version.js.map
|
package/dist/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,iBAAiB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAExD,MAAM,CAAC,MAAM,OAAO,GAAW,GAAG,CAAC,OAAO,CAAC","sourcesContent":["import pkg from \"../package.json\" with { type: \"json\" };\n\nexport const VERSION: string = pkg.version;\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Adaptor, CallToolResponse, FileMetadata, HostContext, HostContextStore, OpenExternalOptions, RequestDisplayMode, RequestModalOptions,
|
|
1
|
+
import type { Adaptor, CallToolResponse, FileMetadata, HostContext, HostContextStore, OpenExternalOptions, RequestDisplayMode, RequestModalOptions, RequestSizeOptions, SendFollowUpMessageOptions, SetViewStateAction, UploadFileOptions } from "../types.js";
|
|
2
2
|
export declare class AppsSdkAdaptor implements Adaptor {
|
|
3
3
|
private static instance;
|
|
4
4
|
static getInstance(): AppsSdkAdaptor;
|
|
@@ -8,9 +8,11 @@ export declare class AppsSdkAdaptor implements Adaptor {
|
|
|
8
8
|
requestDisplayMode: (mode: RequestDisplayMode) => Promise<{
|
|
9
9
|
mode: RequestDisplayMode;
|
|
10
10
|
}>;
|
|
11
|
-
|
|
11
|
+
requestClose: () => Promise<void>;
|
|
12
|
+
requestSize: (_size: RequestSizeOptions) => Promise<void>;
|
|
13
|
+
sendFollowUpMessage: (prompt: string, options?: SendFollowUpMessageOptions) => Promise<void>;
|
|
12
14
|
openExternal(href: string, options?: OpenExternalOptions): void;
|
|
13
|
-
|
|
15
|
+
setViewState: (stateOrUpdater: SetViewStateAction) => Promise<void>;
|
|
14
16
|
uploadFile: (file: File, options?: UploadFileOptions) => Promise<FileMetadata>;
|
|
15
17
|
getFileDownloadUrl: (file: {
|
|
16
18
|
fileId: string;
|
|
@@ -12,12 +12,18 @@ export class AppsSdkAdaptor {
|
|
|
12
12
|
}
|
|
13
13
|
getHostContextStore(key) {
|
|
14
14
|
const bridge = AppsSdkBridge.getInstance();
|
|
15
|
-
if (key === "
|
|
15
|
+
if (key === "viewState") {
|
|
16
16
|
return {
|
|
17
17
|
subscribe: bridge.subscribe("widgetState"),
|
|
18
18
|
getSnapshot: () => bridge.getSnapshot("widgetState")?.modelContent ?? null,
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
+
if (key === "display") {
|
|
22
|
+
return {
|
|
23
|
+
subscribe: bridge.subscribe("view"),
|
|
24
|
+
getSnapshot: () => bridge.getSnapshot("view"),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
21
27
|
return {
|
|
22
28
|
subscribe: bridge.subscribe(key),
|
|
23
29
|
getSnapshot: () => bridge.getSnapshot(key),
|
|
@@ -29,13 +35,22 @@ export class AppsSdkAdaptor {
|
|
|
29
35
|
requestDisplayMode = (mode) => {
|
|
30
36
|
return window.openai.requestDisplayMode({ mode });
|
|
31
37
|
};
|
|
32
|
-
|
|
33
|
-
return window.openai.
|
|
38
|
+
requestClose = () => {
|
|
39
|
+
return window.openai.requestClose();
|
|
40
|
+
};
|
|
41
|
+
requestSize = async (_size) => {
|
|
42
|
+
console.warn("[skybridge] requestSize: not supported on Apps SDK");
|
|
43
|
+
};
|
|
44
|
+
sendFollowUpMessage = (prompt, options) => {
|
|
45
|
+
return window.openai.sendFollowUpMessage({
|
|
46
|
+
prompt,
|
|
47
|
+
scrollToBottom: options?.scrollToBottom,
|
|
48
|
+
});
|
|
34
49
|
};
|
|
35
50
|
openExternal(href, options = {}) {
|
|
36
51
|
window.openai.openExternal({ href, ...options });
|
|
37
52
|
}
|
|
38
|
-
|
|
53
|
+
setViewState = (stateOrUpdater) => {
|
|
39
54
|
const modelContent = typeof stateOrUpdater === "function"
|
|
40
55
|
? stateOrUpdater(window.openai.widgetState?.modelContent ?? null)
|
|
41
56
|
: stateOrUpdater;
|