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.
Files changed (231) hide show
  1. package/README.md +8 -8
  2. package/dist/cli/detect-port.js.map +1 -1
  3. package/dist/cli/header.js.map +1 -1
  4. package/dist/cli/run-command.js.map +1 -1
  5. package/dist/cli/telemetry.js.map +1 -1
  6. package/dist/cli/tunnel-control-server.d.ts +9 -0
  7. package/dist/cli/tunnel-control-server.js +31 -0
  8. package/dist/cli/tunnel-control-server.js.map +1 -0
  9. package/dist/cli/tunnel-control-server.test.js +39 -0
  10. package/dist/cli/tunnel-control-server.test.js.map +1 -0
  11. package/dist/cli/tunnel-handler.d.ts +3 -0
  12. package/dist/cli/tunnel-handler.js +48 -0
  13. package/dist/cli/tunnel-handler.js.map +1 -0
  14. package/dist/cli/tunnel-handler.test.js +105 -0
  15. package/dist/cli/tunnel-handler.test.js.map +1 -0
  16. package/dist/cli/tunnel.d.ts +57 -0
  17. package/dist/cli/tunnel.js +154 -0
  18. package/dist/cli/tunnel.js.map +1 -0
  19. package/dist/cli/tunnel.test.js +190 -0
  20. package/dist/cli/tunnel.test.js.map +1 -0
  21. package/dist/cli/types.js.map +1 -1
  22. package/dist/cli/use-execute-steps.js.map +1 -1
  23. package/dist/cli/use-messages.js.map +1 -1
  24. package/dist/cli/use-nodemon.js +11 -2
  25. package/dist/cli/use-nodemon.js.map +1 -1
  26. package/dist/cli/use-open-browser.d.ts +1 -0
  27. package/dist/cli/use-open-browser.js +44 -0
  28. package/dist/cli/use-open-browser.js.map +1 -0
  29. package/dist/cli/use-tunnel.d.ts +1 -1
  30. package/dist/cli/use-tunnel.js +102 -68
  31. package/dist/cli/use-tunnel.js.map +1 -1
  32. package/dist/cli/use-typescript-check.d.ts +1 -0
  33. package/dist/cli/use-typescript-check.js +41 -6
  34. package/dist/cli/use-typescript-check.js.map +1 -1
  35. package/dist/commands/build.js +62 -7
  36. package/dist/commands/build.js.map +1 -1
  37. package/dist/commands/dev.d.ts +1 -0
  38. package/dist/commands/dev.js +33 -2
  39. package/dist/commands/dev.js.map +1 -1
  40. package/dist/commands/start.js.map +1 -1
  41. package/dist/commands/telemetry/disable.js.map +1 -1
  42. package/dist/commands/telemetry/enable.js.map +1 -1
  43. package/dist/commands/telemetry/status.js.map +1 -1
  44. package/dist/server/asset-base-url-transform-plugin.js +1 -1
  45. package/dist/server/asset-base-url-transform-plugin.js.map +1 -1
  46. package/dist/server/asset-base-url-transform-plugin.test.js +29 -0
  47. package/dist/server/asset-base-url-transform-plugin.test.js.map +1 -1
  48. package/dist/server/content-helpers.js.map +1 -1
  49. package/dist/server/content-helpers.test.js +1 -1
  50. package/dist/server/content-helpers.test.js.map +1 -1
  51. package/dist/server/express.d.ts +1 -5
  52. package/dist/server/express.js +34 -10
  53. package/dist/server/express.js.map +1 -1
  54. package/dist/server/express.test.js +249 -71
  55. package/dist/server/express.test.js.map +1 -1
  56. package/dist/server/index.d.ts +2 -2
  57. package/dist/server/index.js +1 -1
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/inferUtilityTypes.d.ts +6 -6
  60. package/dist/server/inferUtilityTypes.js.map +1 -1
  61. package/dist/server/metric.js.map +1 -1
  62. package/dist/server/middleware.js.map +1 -1
  63. package/dist/server/middleware.test-d.js.map +1 -1
  64. package/dist/server/middleware.test.js.map +1 -1
  65. package/dist/server/server.d.ts +90 -40
  66. package/dist/server/server.js +150 -77
  67. package/dist/server/server.js.map +1 -1
  68. package/dist/server/templateHelper.d.ts +5 -7
  69. package/dist/server/templateHelper.js +3 -22
  70. package/dist/server/templateHelper.js.map +1 -1
  71. package/dist/server/templates.generated.d.ts +4 -0
  72. package/dist/server/templates.generated.js +47 -0
  73. package/dist/server/templates.generated.js.map +1 -0
  74. package/dist/server/tunnel-proxy-router.d.ts +7 -0
  75. package/dist/server/tunnel-proxy-router.js +110 -0
  76. package/dist/server/tunnel-proxy-router.js.map +1 -0
  77. package/dist/server/tunnel-proxy-router.test.js +229 -0
  78. package/dist/server/tunnel-proxy-router.test.js.map +1 -0
  79. package/dist/server/viewsDevServer.d.ts +14 -0
  80. package/dist/server/{widgetsDevServer.js → viewsDevServer.js} +17 -5
  81. package/dist/server/viewsDevServer.js.map +1 -0
  82. package/dist/test/utils.d.ts +7 -7
  83. package/dist/test/utils.js +21 -21
  84. package/dist/test/utils.js.map +1 -1
  85. package/dist/test/view.test.d.ts +1 -0
  86. package/dist/test/{widget.test.js → view.test.js} +128 -59
  87. package/dist/test/view.test.js.map +1 -0
  88. package/dist/version.js +1 -3
  89. package/dist/version.js.map +1 -1
  90. package/dist/web/bridges/apps-sdk/adaptor.d.ts +5 -3
  91. package/dist/web/bridges/apps-sdk/adaptor.js +19 -4
  92. package/dist/web/bridges/apps-sdk/adaptor.js.map +1 -1
  93. package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
  94. package/dist/web/bridges/apps-sdk/index.js.map +1 -1
  95. package/dist/web/bridges/apps-sdk/types.d.ts +8 -1
  96. package/dist/web/bridges/apps-sdk/types.js.map +1 -1
  97. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -1
  98. package/dist/web/bridges/get-adaptor.js.map +1 -1
  99. package/dist/web/bridges/index.js.map +1 -1
  100. package/dist/web/bridges/mcp-app/adaptor.d.ts +9 -7
  101. package/dist/web/bridges/mcp-app/adaptor.js +33 -30
  102. package/dist/web/bridges/mcp-app/adaptor.js.map +1 -1
  103. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  104. package/dist/web/bridges/mcp-app/index.js.map +1 -1
  105. package/dist/web/bridges/mcp-app/types.js.map +1 -1
  106. package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
  107. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -1
  108. package/dist/web/bridges/types.d.ts +17 -9
  109. package/dist/web/bridges/types.js.map +1 -1
  110. package/dist/web/bridges/use-host-context.js.map +1 -1
  111. package/dist/web/components/modal-provider.js +1 -1
  112. package/dist/web/components/modal-provider.js.map +1 -1
  113. package/dist/web/create-store.js +9 -9
  114. package/dist/web/create-store.js.map +1 -1
  115. package/dist/web/create-store.test.js +14 -16
  116. package/dist/web/create-store.test.js.map +1 -1
  117. package/dist/web/data-llm.d.ts +1 -1
  118. package/dist/web/data-llm.js +3 -3
  119. package/dist/web/data-llm.js.map +1 -1
  120. package/dist/web/data-llm.test.js +22 -22
  121. package/dist/web/data-llm.test.js.map +1 -1
  122. package/dist/web/generate-helpers.d.ts +14 -14
  123. package/dist/web/generate-helpers.js +14 -14
  124. package/dist/web/generate-helpers.js.map +1 -1
  125. package/dist/web/generate-helpers.test-d.js +26 -26
  126. package/dist/web/generate-helpers.test-d.js.map +1 -1
  127. package/dist/web/generate-helpers.test.js.map +1 -1
  128. package/dist/web/helpers/state.d.ts +2 -2
  129. package/dist/web/helpers/state.js +11 -11
  130. package/dist/web/helpers/state.js.map +1 -1
  131. package/dist/web/helpers/state.test.js +9 -9
  132. package/dist/web/helpers/state.test.js.map +1 -1
  133. package/dist/web/hooks/index.d.ts +3 -1
  134. package/dist/web/hooks/index.js +3 -1
  135. package/dist/web/hooks/index.js.map +1 -1
  136. package/dist/web/hooks/test/utils.js.map +1 -1
  137. package/dist/web/hooks/use-call-tool.js.map +1 -1
  138. package/dist/web/hooks/use-call-tool.test-d.js.map +1 -1
  139. package/dist/web/hooks/use-call-tool.test.js +0 -4
  140. package/dist/web/hooks/use-call-tool.test.js.map +1 -1
  141. package/dist/web/hooks/use-display-mode.js.map +1 -1
  142. package/dist/web/hooks/use-display-mode.test-d.js.map +1 -1
  143. package/dist/web/hooks/use-display-mode.test.js.map +1 -1
  144. package/dist/web/hooks/use-files.js.map +1 -1
  145. package/dist/web/hooks/use-files.test.js.map +1 -1
  146. package/dist/web/hooks/use-layout.js.map +1 -1
  147. package/dist/web/hooks/use-layout.test.js.map +1 -1
  148. package/dist/web/hooks/use-open-external.js.map +1 -1
  149. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  150. package/dist/web/hooks/use-request-close.d.ts +2 -0
  151. package/dist/web/hooks/use-request-close.js +8 -0
  152. package/dist/web/hooks/use-request-close.js.map +1 -0
  153. package/dist/web/hooks/use-request-close.test.d.ts +1 -0
  154. package/dist/web/hooks/use-request-close.test.js +52 -0
  155. package/dist/web/hooks/use-request-close.test.js.map +1 -0
  156. package/dist/web/hooks/use-request-modal.d.ts +1 -1
  157. package/dist/web/hooks/use-request-modal.js +4 -4
  158. package/dist/web/hooks/use-request-modal.js.map +1 -1
  159. package/dist/web/hooks/use-request-modal.test.js +1 -1
  160. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  161. package/dist/web/hooks/use-request-size.d.ts +3 -0
  162. package/dist/web/hooks/use-request-size.js +8 -0
  163. package/dist/web/hooks/use-request-size.js.map +1 -0
  164. package/dist/web/hooks/use-request-size.test.d.ts +1 -0
  165. package/dist/web/hooks/use-request-size.test.js +65 -0
  166. package/dist/web/hooks/use-request-size.test.js.map +1 -0
  167. package/dist/web/hooks/use-send-follow-up-message.d.ts +2 -1
  168. package/dist/web/hooks/use-send-follow-up-message.js +2 -2
  169. package/dist/web/hooks/use-send-follow-up-message.js.map +1 -1
  170. package/dist/web/hooks/use-set-open-in-app-url.js.map +1 -1
  171. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  172. package/dist/web/hooks/use-tool-info.js.map +1 -1
  173. package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
  174. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  175. package/dist/web/hooks/use-user.js.map +1 -1
  176. package/dist/web/hooks/use-user.test.js.map +1 -1
  177. package/dist/web/hooks/use-view-state.d.ts +4 -0
  178. package/dist/web/hooks/use-view-state.js +32 -0
  179. package/dist/web/hooks/use-view-state.js.map +1 -0
  180. package/dist/web/hooks/use-view-state.test.d.ts +1 -0
  181. package/dist/web/hooks/{use-widget-state.test.js → use-view-state.test.js} +17 -17
  182. package/dist/web/hooks/use-view-state.test.js.map +1 -0
  183. package/dist/web/index.d.ts +1 -3
  184. package/dist/web/index.js +1 -2
  185. package/dist/web/index.js.map +1 -1
  186. package/dist/web/mount-view.d.ts +1 -0
  187. package/dist/web/{mount-widget.js → mount-view.js} +2 -2
  188. package/dist/web/mount-view.js.map +1 -0
  189. package/dist/web/plugin/data-llm.test.js.map +1 -1
  190. package/dist/web/plugin/plugin.d.ts +1 -1
  191. package/dist/web/plugin/plugin.js +87 -41
  192. package/dist/web/plugin/plugin.js.map +1 -1
  193. package/dist/web/plugin/scan-views.d.ts +16 -0
  194. package/dist/web/plugin/scan-views.js +88 -0
  195. package/dist/web/plugin/scan-views.js.map +1 -0
  196. package/dist/web/plugin/scan-views.test.d.ts +1 -0
  197. package/dist/web/plugin/scan-views.test.js +99 -0
  198. package/dist/web/plugin/scan-views.test.js.map +1 -0
  199. package/dist/web/plugin/transform-data-llm.js.map +1 -1
  200. package/dist/web/plugin/transform-data-llm.test.js.map +1 -1
  201. package/dist/web/plugin/{validate-widget.js → validate-view.js} +1 -1
  202. package/dist/web/plugin/validate-view.js.map +1 -0
  203. package/dist/web/plugin/validate-view.test.d.ts +1 -0
  204. package/dist/web/plugin/{validate-widget.test.js → validate-view.test.js} +6 -6
  205. package/dist/web/plugin/validate-view.test.js.map +1 -0
  206. package/dist/web/proxy.js.map +1 -1
  207. package/dist/web/types.js.map +1 -1
  208. package/package.json +26 -20
  209. package/dist/server/templates/development.hbs +0 -12
  210. package/dist/server/templates/production.hbs +0 -6
  211. package/dist/server/widgetsDevServer.d.ts +0 -3
  212. package/dist/server/widgetsDevServer.js.map +0 -1
  213. package/dist/test/widget.test.js.map +0 -1
  214. package/dist/web/hooks/use-widget-state.d.ts +0 -4
  215. package/dist/web/hooks/use-widget-state.js +0 -32
  216. package/dist/web/hooks/use-widget-state.js.map +0 -1
  217. package/dist/web/hooks/use-widget-state.test.js.map +0 -1
  218. package/dist/web/mount-widget.d.ts +0 -1
  219. package/dist/web/mount-widget.js.map +0 -1
  220. package/dist/web/plugin/scan-widgets.d.ts +0 -8
  221. package/dist/web/plugin/scan-widgets.js +0 -68
  222. package/dist/web/plugin/scan-widgets.js.map +0 -1
  223. package/dist/web/plugin/scan-widgets.test.js +0 -96
  224. package/dist/web/plugin/scan-widgets.test.js.map +0 -1
  225. package/dist/web/plugin/validate-widget.js.map +0 -1
  226. package/dist/web/plugin/validate-widget.test.js.map +0 -1
  227. /package/dist/{test/widget.test.d.ts → cli/tunnel-control-server.test.d.ts} +0 -0
  228. /package/dist/{web/hooks/use-widget-state.test.d.ts → cli/tunnel-handler.test.d.ts} +0 -0
  229. /package/dist/{web/plugin/scan-widgets.test.d.ts → cli/tunnel.test.d.ts} +0 -0
  230. /package/dist/{web/plugin/validate-widget.test.d.ts → server/tunnel-proxy-router.test.d.ts} +0 -0
  231. /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:widget:my-widget": {
6
- file: "assets/my-widget-abc123.js",
7
- name: "my-widget",
5
+ "skybridge:view:my-view": {
6
+ file: "assets/my-view-abc123.js",
7
+ name: "my-view",
8
8
  isEntry: true,
9
9
  },
10
- "skybridge:widget:folder-widget": {
11
- file: "assets/folder-widget-def456.js",
12
- name: "folder-widget",
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-widget",
60
+ name: "my-view",
49
61
  description: "Test tool",
50
62
  view: {
51
- component: "my-widget",
52
- description: "Test widget",
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://widgets/apps-sdk/my-widget.html"), mockExtra);
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://widgets/apps-sdk/my-widget.html",
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 widget",
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/widget/my-widget`);
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-widget",
100
+ name: "my-view",
89
101
  description: "Test tool",
90
102
  view: {
91
- component: "my-widget",
92
- description: "Test widget",
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 result = await appsSdkResourceCallback(new URL("ui://widgets/apps-sdk/my-widget.html"), mockExtra);
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: "ui://widgets/apps-sdk/my-widget.html",
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 widget",
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-widget-abc123.js`);
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 widget domains", async () => {
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-widget",
140
+ name: "my-view",
128
141
  description: "Test tool",
129
- view: { component: "my-widget", description: "Test widget" },
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("ui://widgets/ext-apps/my-widget.html"), createMockExtra("localhost:3000", {
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 widget",
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-widget",
174
+ name: "my-view",
162
175
  description: "Test tool",
163
176
  view: {
164
- component: "my-widget",
165
- description: "Test widget",
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://widgets/apps-sdk/my-widget.html"), createMockExtra(host));
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://widgets/apps-sdk/my-widget.html",
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 widget",
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://widgets/ext-apps/my-widget.html"), createMockExtra(host));
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://widgets/ext-apps/my-widget.html",
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 widget",
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-widget",
237
+ name: "my-view",
225
238
  description: "Test tool",
226
- view: { component: "my-widget", description: "Test widget" },
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://widgets/ext-apps/my-widget.html",
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-widget",
251
+ name: "my-view",
239
252
  description: "Test tool",
240
253
  view: {
241
- component: "my-widget",
242
- description: "Test widget",
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://widgets/apps-sdk/my-widget.html");
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-widget",
323
+ name: "my-view",
255
324
  description: "Test tool",
256
325
  view: {
257
- component: "my-widget",
258
- description: "Test widget",
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://widgets/ext-apps/my-widget.html",
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-widget",
346
+ name: "my-view",
278
347
  description: "Test tool",
279
- view: { component: "my-widget", description: "Test widget" },
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-widget",
365
+ name: "my-view",
297
366
  description: "Test tool",
298
- view: { component: "my-widget", description: "Test widget" },
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-widget",
381
+ name: "my-view",
313
382
  description: "Test tool",
314
- view: { component: "my-widget", description: "Test widget" },
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-widget constraint", () => {
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: widget "magic-8-ball" is already used by tool "shake"');
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 widget",
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 widget",
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://widgets/apps-sdk/csp-tool.html"), createMockExtra(host));
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 widget",
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://widgets/apps-sdk/override-tool.html"), createMockExtra("localhost:3000"));
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=widget.test.js.map
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 { createRequire } from "node:module";
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
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,GAAG,GAAG,OAAO,CAAC,iBAAiB,CAAwB,CAAC;AAE9D,MAAM,CAAC,MAAM,OAAO,GAAW,GAAG,CAAC,OAAO,CAAC"}
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, SetWidgetStateAction, UploadFileOptions } from "../types.js";
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
- sendFollowUpMessage: (prompt: string) => Promise<void>;
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
- setWidgetState: (stateOrUpdater: SetWidgetStateAction) => Promise<void>;
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 === "widgetState") {
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
- sendFollowUpMessage = (prompt) => {
33
- return window.openai.sendFollowUpMessage({ prompt });
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
- setWidgetState = (stateOrUpdater) => {
53
+ setViewState = (stateOrUpdater) => {
39
54
  const modelContent = typeof stateOrUpdater === "function"
40
55
  ? stateOrUpdater(window.openai.widgetState?.modelContent ?? null)
41
56
  : stateOrUpdater;