skybridge 0.0.0-dev.89e1390 → 0.0.0-dev.89e35fa

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 (123) hide show
  1. package/dist/cli/use-open-tunnel-browser.d.ts +6 -0
  2. package/dist/cli/use-open-tunnel-browser.js +19 -0
  3. package/dist/cli/use-open-tunnel-browser.js.map +1 -0
  4. package/dist/commands/dev.js +3 -1
  5. package/dist/commands/dev.js.map +1 -1
  6. package/dist/server/express.test.js +0 -31
  7. package/dist/server/express.test.js.map +1 -1
  8. package/dist/server/index.d.ts +1 -2
  9. package/dist/server/index.js.map +1 -1
  10. package/dist/server/server.d.ts +14 -29
  11. package/dist/server/server.js +80 -163
  12. package/dist/server/server.js.map +1 -1
  13. package/dist/test/view.test.js +57 -99
  14. package/dist/test/view.test.js.map +1 -1
  15. package/dist/web/bridges/adaptor.d.ts +50 -0
  16. package/dist/web/bridges/adaptor.js +314 -0
  17. package/dist/web/bridges/adaptor.js.map +1 -0
  18. package/dist/web/bridges/adaptor.test.js +205 -0
  19. package/dist/web/bridges/adaptor.test.js.map +1 -0
  20. package/dist/web/bridges/apps-sdk/bridge.d.ts +1 -1
  21. package/dist/web/bridges/apps-sdk/bridge.js +3 -4
  22. package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
  23. package/dist/web/bridges/apps-sdk/index.d.ts +0 -1
  24. package/dist/web/bridges/apps-sdk/index.js +0 -1
  25. package/dist/web/bridges/apps-sdk/index.js.map +1 -1
  26. package/dist/web/bridges/get-adaptor.d.ts +6 -4
  27. package/dist/web/bridges/get-adaptor.js +16 -9
  28. package/dist/web/bridges/get-adaptor.js.map +1 -1
  29. package/dist/web/bridges/get-adaptor.test.js +32 -0
  30. package/dist/web/bridges/get-adaptor.test.js.map +1 -0
  31. package/dist/web/bridges/host-context-stores.d.ts +27 -0
  32. package/dist/web/bridges/host-context-stores.js +73 -0
  33. package/dist/web/bridges/host-context-stores.js.map +1 -0
  34. package/dist/web/bridges/mcp-app/bridge.d.ts +3 -4
  35. package/dist/web/bridges/mcp-app/bridge.js +2 -26
  36. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  37. package/dist/web/bridges/mcp-app/bridge.test.js +21 -0
  38. package/dist/web/bridges/mcp-app/bridge.test.js.map +1 -0
  39. package/dist/web/bridges/mcp-app/index.d.ts +0 -1
  40. package/dist/web/bridges/mcp-app/index.js +0 -1
  41. package/dist/web/bridges/mcp-app/index.js.map +1 -1
  42. package/dist/web/bridges/types.d.ts +16 -34
  43. package/dist/web/bridges/types.js +14 -1
  44. package/dist/web/bridges/types.js.map +1 -1
  45. package/dist/web/bridges/types.test.d.ts +1 -0
  46. package/dist/web/bridges/types.test.js +19 -0
  47. package/dist/web/bridges/types.test.js.map +1 -0
  48. package/dist/web/components/modal-provider.js +4 -3
  49. package/dist/web/components/modal-provider.js.map +1 -1
  50. package/dist/web/create-store.test.js +7 -5
  51. package/dist/web/create-store.test.js.map +1 -1
  52. package/dist/web/data-llm.test.js +8 -5
  53. package/dist/web/data-llm.test.js.map +1 -1
  54. package/dist/web/generate-helpers.test-d.js +2 -4
  55. package/dist/web/generate-helpers.test-d.js.map +1 -1
  56. package/dist/web/hooks/index.d.ts +0 -1
  57. package/dist/web/hooks/index.js +0 -1
  58. package/dist/web/hooks/index.js.map +1 -1
  59. package/dist/web/hooks/use-call-tool.test.js +36 -23
  60. package/dist/web/hooks/use-call-tool.test.js.map +1 -1
  61. package/dist/web/hooks/use-display-mode.test.js +55 -20
  62. package/dist/web/hooks/use-display-mode.test.js.map +1 -1
  63. package/dist/web/hooks/use-download.test.js +11 -3
  64. package/dist/web/hooks/use-download.test.js.map +1 -1
  65. package/dist/web/hooks/use-files.test.js +10 -2
  66. package/dist/web/hooks/use-files.test.js.map +1 -1
  67. package/dist/web/hooks/use-layout.test.js +59 -26
  68. package/dist/web/hooks/use-layout.test.js.map +1 -1
  69. package/dist/web/hooks/use-open-external.test.js +19 -5
  70. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  71. package/dist/web/hooks/use-request-close.test.js +34 -40
  72. package/dist/web/hooks/use-request-close.test.js.map +1 -1
  73. package/dist/web/hooks/use-request-modal.test.js +10 -0
  74. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  75. package/dist/web/hooks/use-request-size.test.js +35 -53
  76. package/dist/web/hooks/use-request-size.test.js.map +1 -1
  77. package/dist/web/hooks/use-set-open-in-app-url.test.js +28 -8
  78. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  79. package/dist/web/hooks/use-tool-info.d.ts +7 -25
  80. package/dist/web/hooks/use-tool-info.js +8 -5
  81. package/dist/web/hooks/use-tool-info.js.map +1 -1
  82. package/dist/web/hooks/use-tool-info.test-d.js +29 -11
  83. package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
  84. package/dist/web/hooks/use-tool-info.test.js +42 -32
  85. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  86. package/dist/web/hooks/use-user.test.js +81 -35
  87. package/dist/web/hooks/use-user.test.js.map +1 -1
  88. package/dist/web/hooks/use-view-state.test.js +6 -2
  89. package/dist/web/hooks/use-view-state.test.js.map +1 -1
  90. package/package.json +3 -4
  91. package/dist/server/auth/index.d.ts +0 -20
  92. package/dist/server/auth/index.js +0 -2
  93. package/dist/server/auth/index.js.map +0 -1
  94. package/dist/server/auth/setup.d.ts +0 -4
  95. package/dist/server/auth/setup.js +0 -52
  96. package/dist/server/auth/setup.js.map +0 -1
  97. package/dist/server/auth/setup.test.js +0 -210
  98. package/dist/server/auth/setup.test.js.map +0 -1
  99. package/dist/server/auth/verify.d.ts +0 -11
  100. package/dist/server/auth/verify.js +0 -37
  101. package/dist/server/auth/verify.js.map +0 -1
  102. package/dist/server/auth/verify.test.js +0 -87
  103. package/dist/server/auth/verify.test.js.map +0 -1
  104. package/dist/server/requestOrigin.d.ts +0 -6
  105. package/dist/server/requestOrigin.js +0 -30
  106. package/dist/server/requestOrigin.js.map +0 -1
  107. package/dist/server/view-resource-resolution.test.d.ts +0 -6
  108. package/dist/server/view-resource-resolution.test.js +0 -88
  109. package/dist/server/view-resource-resolution.test.js.map +0 -1
  110. package/dist/web/bridges/apps-sdk/adaptor.d.ts +0 -29
  111. package/dist/web/bridges/apps-sdk/adaptor.js +0 -117
  112. package/dist/web/bridges/apps-sdk/adaptor.js.map +0 -1
  113. package/dist/web/bridges/mcp-app/adaptor.d.ts +0 -53
  114. package/dist/web/bridges/mcp-app/adaptor.js +0 -283
  115. package/dist/web/bridges/mcp-app/adaptor.js.map +0 -1
  116. package/dist/web/bridges/mcp-app/view-tools.test.js +0 -144
  117. package/dist/web/bridges/mcp-app/view-tools.test.js.map +0 -1
  118. package/dist/web/hooks/use-register-view-tool.d.ts +0 -38
  119. package/dist/web/hooks/use-register-view-tool.js +0 -50
  120. package/dist/web/hooks/use-register-view-tool.js.map +0 -1
  121. /package/dist/{server/auth/setup.test.d.ts → web/bridges/adaptor.test.d.ts} +0 -0
  122. /package/dist/{server/auth/verify.test.d.ts → web/bridges/get-adaptor.test.d.ts} +0 -0
  123. /package/dist/web/bridges/mcp-app/{view-tools.test.d.ts → bridge.test.d.ts} +0 -0
@@ -0,0 +1,314 @@
1
+ import { AppsSdkBridge } from "./apps-sdk/bridge.js";
2
+ import { buildAppsSdkOverlayStores, buildMcpContextStores, } from "./host-context-stores.js";
3
+ import { McpAppBridge } from "./mcp-app/bridge.js";
4
+ import { NotSupportedError } from "./types.js";
5
+ const STORAGE_PREFIX = "sb:";
6
+ const MAX_STORAGE_ENTRIES = 200;
7
+ function findStorageKey(viewUUID) {
8
+ const suffix = `:${viewUUID}`;
9
+ for (let i = 0; i < localStorage.length; i++) {
10
+ const key = localStorage.key(i);
11
+ if (key?.startsWith(STORAGE_PREFIX) && key.endsWith(suffix)) {
12
+ return key;
13
+ }
14
+ }
15
+ return undefined;
16
+ }
17
+ /**
18
+ * @internal
19
+ * Single composite implementation of {@link Adaptor}. Composes the MCP App
20
+ * bridge (always present) with an optional `window.openai` overlay. Per-method
21
+ * routing rules are encoded inline.
22
+ */
23
+ export class HostAdaptor {
24
+ mcp;
25
+ openai;
26
+ appsSdkBridge;
27
+ mcpStores;
28
+ appsSdkStores;
29
+ _viewState = null;
30
+ viewStateListeners = new Set();
31
+ _viewUUID = null;
32
+ _polyfillDisplay = { mode: "inline" };
33
+ polyfillDisplayListeners = new Set();
34
+ polyfillDisplayStore;
35
+ polyfillViewStateStore;
36
+ unsubscribeViewUUID = null;
37
+ constructor() {
38
+ this.mcp = McpAppBridge.getInstance();
39
+ this.mcpStores = buildMcpContextStores(this.mcp);
40
+ if (typeof window !== "undefined" && window.openai !== undefined) {
41
+ this.openai = window.openai;
42
+ this.appsSdkBridge = AppsSdkBridge.getInstance();
43
+ this.appsSdkStores = buildAppsSdkOverlayStores(this.appsSdkBridge);
44
+ }
45
+ else {
46
+ this.openai = null;
47
+ this.appsSdkBridge = null;
48
+ this.appsSdkStores = null;
49
+ }
50
+ // Built once so that getHostContextStore returns stable references — required
51
+ // by useSyncExternalStore to avoid resubscribing on every render.
52
+ this.polyfillDisplayStore = {
53
+ subscribe: (onChange) => {
54
+ this.polyfillDisplayListeners.add(onChange);
55
+ return () => {
56
+ this.polyfillDisplayListeners.delete(onChange);
57
+ };
58
+ },
59
+ getSnapshot: () => this._polyfillDisplay,
60
+ };
61
+ this.polyfillViewStateStore = {
62
+ subscribe: (onChange) => {
63
+ this.viewStateListeners.add(onChange);
64
+ return () => {
65
+ this.viewStateListeners.delete(onChange);
66
+ };
67
+ },
68
+ getSnapshot: () => this._viewState,
69
+ };
70
+ this.subscribeToViewUUID();
71
+ }
72
+ hasAppsSdkOverlay() {
73
+ return this.openai !== null;
74
+ }
75
+ /** @internal Release any subscriptions held on the underlying bridges. */
76
+ cleanup() {
77
+ this.unsubscribeViewUUID?.();
78
+ this.unsubscribeViewUUID = null;
79
+ }
80
+ // ---- Adaptor interface (stubs) ----
81
+ getHostContextStore = (key) => {
82
+ if (this.appsSdkStores && (key === "display" || key === "viewState")) {
83
+ return this.appsSdkStores[key];
84
+ }
85
+ if (key === "display") {
86
+ return this.polyfillDisplayStore;
87
+ }
88
+ if (key === "viewState") {
89
+ return this.polyfillViewStateStore;
90
+ }
91
+ return this.mcpStores[key];
92
+ };
93
+ callTool = async (name, args) => {
94
+ const app = await this.mcp.getApp();
95
+ const response = await app.callServerTool({
96
+ name,
97
+ arguments: args ?? undefined,
98
+ });
99
+ return {
100
+ content: response.content,
101
+ structuredContent: response.structuredContent ?? {},
102
+ isError: response.isError ?? false,
103
+ meta: response._meta ?? {},
104
+ };
105
+ };
106
+ requestDisplayMode = async (mode) => {
107
+ const app = await this.mcp.getApp();
108
+ return app.requestDisplayMode({ mode });
109
+ };
110
+ requestClose = async () => {
111
+ const app = await this.mcp.getApp();
112
+ await app.requestTeardown();
113
+ };
114
+ requestSize = async (size) => {
115
+ const app = await this.mcp.getApp();
116
+ await app.sendSizeChanged(size);
117
+ };
118
+ sendFollowUpMessage = async (prompt, options) => {
119
+ if (this.openai && options?.scrollToBottom !== undefined) {
120
+ await this.openai.sendFollowUpMessage({
121
+ prompt,
122
+ scrollToBottom: options.scrollToBottom,
123
+ });
124
+ return;
125
+ }
126
+ const app = await this.mcp.getApp();
127
+ await app.sendMessage({
128
+ role: "user",
129
+ content: [{ type: "text", text: prompt }],
130
+ });
131
+ };
132
+ openExternal = (href, options) => {
133
+ if (this.openai && options?.redirectUrl === false) {
134
+ this.openai.openExternal({ href, redirectUrl: false });
135
+ return;
136
+ }
137
+ this.mcp
138
+ .getApp()
139
+ .then((app) => app.openLink({ url: href }))
140
+ .catch((err) => {
141
+ console.error("Failed to open external link:", err);
142
+ });
143
+ };
144
+ download = async (params) => {
145
+ const app = await this.mcp.getApp();
146
+ if (!app.getHostCapabilities()?.downloadFile) {
147
+ console.error("[skybridge] download: host does not support ui/download-file");
148
+ return { isError: true };
149
+ }
150
+ return app.downloadFile(params);
151
+ };
152
+ setViewState = async (stateOrUpdater) => {
153
+ if (this.openai) {
154
+ const modelContent = typeof stateOrUpdater === "function"
155
+ ? stateOrUpdater(this.openai.widgetState?.modelContent ?? null)
156
+ : stateOrUpdater;
157
+ await this.openai.setWidgetState({
158
+ privateContent: {},
159
+ ...this.openai.widgetState,
160
+ modelContent,
161
+ });
162
+ return;
163
+ }
164
+ const newState = typeof stateOrUpdater === "function"
165
+ ? stateOrUpdater(this._viewState)
166
+ : stateOrUpdater;
167
+ // update local state immediately so successive calls see fresh state
168
+ this._viewState = newState;
169
+ this.viewStateListeners.forEach((l) => {
170
+ l();
171
+ });
172
+ this.persistToLocalStorage(newState);
173
+ try {
174
+ const app = await this.mcp.getApp();
175
+ await app.updateModelContext({
176
+ structuredContent: newState,
177
+ content: [{ type: "text", text: JSON.stringify(newState) }],
178
+ });
179
+ }
180
+ catch (error) {
181
+ console.error("Failed to update view state in MCP App.", error);
182
+ }
183
+ };
184
+ uploadFile = async (file, options) => {
185
+ if (!this.openai) {
186
+ throw new NotSupportedError("uploadFile");
187
+ }
188
+ const metadata = await this.openai.uploadFile(file, options);
189
+ await this.trackFileIds(metadata.fileId);
190
+ return metadata;
191
+ };
192
+ getFileDownloadUrl = (file) => {
193
+ if (!this.openai) {
194
+ return Promise.reject(new NotSupportedError("getFileDownloadUrl"));
195
+ }
196
+ return this.openai.getFileDownloadUrl(file);
197
+ };
198
+ selectFiles = async () => {
199
+ if (!this.openai) {
200
+ throw new NotSupportedError("selectFiles");
201
+ }
202
+ if (!this.openai.selectFiles) {
203
+ throw new Error("selectFiles is not supported by the current host version.");
204
+ }
205
+ const files = await this.openai.selectFiles();
206
+ if (files.length > 0) {
207
+ await this.trackFileIds(...files.map((f) => f.fileId));
208
+ }
209
+ return files;
210
+ };
211
+ openModal = (options) => {
212
+ if (this.openai) {
213
+ this.openai.requestModal(options);
214
+ return;
215
+ }
216
+ this._polyfillDisplay = { mode: "modal", params: options.params };
217
+ this.polyfillDisplayListeners.forEach((l) => {
218
+ l();
219
+ });
220
+ };
221
+ setOpenInAppUrl = (href) => {
222
+ if (!this.openai) {
223
+ return Promise.reject(new NotSupportedError("setOpenInAppUrl"));
224
+ }
225
+ const trimmed = href.trim();
226
+ if (!trimmed) {
227
+ return Promise.reject(new Error("The href parameter is required."));
228
+ }
229
+ return this.openai.setOpenInAppUrl({ href: trimmed });
230
+ };
231
+ closeModal = () => {
232
+ if (this.openai) {
233
+ return; // host owns modal lifecycle
234
+ }
235
+ this._polyfillDisplay = { mode: "inline" };
236
+ this.polyfillDisplayListeners.forEach((l) => {
237
+ l();
238
+ });
239
+ };
240
+ async trackFileIds(...fileIds) {
241
+ if (!this.openai) {
242
+ return;
243
+ }
244
+ const current = this.openai.widgetState;
245
+ const state = current
246
+ ? { ...current }
247
+ : { modelContent: {}, privateContent: {} };
248
+ if (!state.imageIds) {
249
+ state.imageIds = [];
250
+ }
251
+ state.imageIds.push(...fileIds);
252
+ await this.openai.setWidgetState(state);
253
+ }
254
+ // ---- viewState persistence helpers (used in subsequent tasks) ----
255
+ subscribeToViewUUID() {
256
+ this.unsubscribeViewUUID = this.mcp.subscribe("toolResult")(() => {
257
+ const toolResult = this.mcp.getSnapshot("toolResult");
258
+ const viewUUID = toolResult?._meta?.viewUUID;
259
+ if (viewUUID && viewUUID !== this._viewUUID) {
260
+ this._viewUUID = viewUUID;
261
+ this.restoreFromLocalStorage(viewUUID);
262
+ }
263
+ });
264
+ }
265
+ restoreFromLocalStorage(viewUUID) {
266
+ try {
267
+ const existingKey = findStorageKey(viewUUID);
268
+ if (existingKey) {
269
+ const stored = localStorage.getItem(existingKey);
270
+ if (stored !== null) {
271
+ this._viewState = JSON.parse(stored);
272
+ this.viewStateListeners.forEach((l) => {
273
+ l();
274
+ });
275
+ }
276
+ }
277
+ }
278
+ catch (err) {
279
+ console.error(err);
280
+ }
281
+ }
282
+ persistToLocalStorage(state) {
283
+ if (!this._viewUUID || state === null) {
284
+ return;
285
+ }
286
+ try {
287
+ const oldKey = findStorageKey(this._viewUUID);
288
+ if (oldKey) {
289
+ localStorage.removeItem(oldKey);
290
+ }
291
+ const newKey = `${STORAGE_PREFIX}${Date.now()}:${this._viewUUID}`;
292
+ localStorage.setItem(newKey, JSON.stringify(state));
293
+ const keys = [];
294
+ for (let i = 0; i < localStorage.length; i++) {
295
+ const key = localStorage.key(i);
296
+ if (key?.startsWith(STORAGE_PREFIX)) {
297
+ keys.push(key);
298
+ }
299
+ }
300
+ if (keys.length <= MAX_STORAGE_ENTRIES) {
301
+ return;
302
+ }
303
+ keys.sort();
304
+ const toRemove = keys.slice(0, keys.length - MAX_STORAGE_ENTRIES);
305
+ for (const key of toRemove) {
306
+ localStorage.removeItem(key);
307
+ }
308
+ }
309
+ catch (err) {
310
+ console.error(err);
311
+ }
312
+ }
313
+ }
314
+ //# sourceMappingURL=adaptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adaptor.js","sourceRoot":"","sources":["../../../src/web/bridges/adaptor.ts"],"names":[],"mappings":"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","sourcesContent":["import { AppsSdkBridge } from \"./apps-sdk/bridge.js\";\nimport type { AppsSdkWidgetState } from \"./apps-sdk/types.js\";\nimport {\n buildAppsSdkOverlayStores,\n buildMcpContextStores,\n} from \"./host-context-stores.js\";\nimport { McpAppBridge } from \"./mcp-app/bridge.js\";\nimport type {\n Adaptor,\n CallToolResponse,\n DownloadParams,\n DownloadResult,\n FileMetadata,\n HostContext,\n HostContextStore,\n OpenExternalOptions,\n RequestDisplayMode,\n RequestModalOptions,\n RequestSizeOptions,\n SendFollowUpMessageOptions,\n SetViewStateAction,\n UploadFileOptions,\n} from \"./types.js\";\nimport { NotSupportedError } from \"./types.js\";\n\nconst STORAGE_PREFIX = \"sb:\";\nconst MAX_STORAGE_ENTRIES = 200;\n\nfunction findStorageKey(viewUUID: string): string | undefined {\n const suffix = `:${viewUUID}`;\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key?.startsWith(STORAGE_PREFIX) && key.endsWith(suffix)) {\n return key;\n }\n }\n return undefined;\n}\n\n/**\n * @internal\n * Single composite implementation of {@link Adaptor}. Composes the MCP App\n * bridge (always present) with an optional `window.openai` overlay. Per-method\n * routing rules are encoded inline.\n */\nexport class HostAdaptor implements Adaptor {\n private readonly mcp: McpAppBridge;\n private readonly openai: typeof window.openai | null;\n private readonly appsSdkBridge: AppsSdkBridge | null;\n\n private readonly mcpStores: ReturnType<typeof buildMcpContextStores>;\n private readonly appsSdkStores: ReturnType<\n typeof buildAppsSdkOverlayStores\n > | null;\n\n private _viewState: HostContext[\"viewState\"] = null;\n private readonly viewStateListeners = new Set<() => void>();\n private _viewUUID: string | null = null;\n\n private _polyfillDisplay: HostContext[\"display\"] = { mode: \"inline\" };\n private readonly polyfillDisplayListeners = new Set<() => void>();\n\n private readonly polyfillDisplayStore: HostContextStore<\"display\">;\n private readonly polyfillViewStateStore: HostContextStore<\"viewState\">;\n\n private unsubscribeViewUUID: (() => void) | null = null;\n\n constructor() {\n this.mcp = McpAppBridge.getInstance();\n this.mcpStores = buildMcpContextStores(this.mcp);\n\n if (typeof window !== \"undefined\" && window.openai !== undefined) {\n this.openai = window.openai;\n this.appsSdkBridge = AppsSdkBridge.getInstance();\n this.appsSdkStores = buildAppsSdkOverlayStores(this.appsSdkBridge);\n } else {\n this.openai = null;\n this.appsSdkBridge = null;\n this.appsSdkStores = null;\n }\n\n // Built once so that getHostContextStore returns stable references — required\n // by useSyncExternalStore to avoid resubscribing on every render.\n this.polyfillDisplayStore = {\n subscribe: (onChange: () => void) => {\n this.polyfillDisplayListeners.add(onChange);\n return () => {\n this.polyfillDisplayListeners.delete(onChange);\n };\n },\n getSnapshot: () => this._polyfillDisplay,\n };\n this.polyfillViewStateStore = {\n subscribe: (onChange: () => void) => {\n this.viewStateListeners.add(onChange);\n return () => {\n this.viewStateListeners.delete(onChange);\n };\n },\n getSnapshot: () => this._viewState,\n };\n\n this.subscribeToViewUUID();\n }\n\n public hasAppsSdkOverlay(): boolean {\n return this.openai !== null;\n }\n\n /** @internal Release any subscriptions held on the underlying bridges. */\n public cleanup(): void {\n this.unsubscribeViewUUID?.();\n this.unsubscribeViewUUID = null;\n }\n\n // ---- Adaptor interface (stubs) ----\n\n public getHostContextStore = <K extends keyof HostContext>(\n key: K,\n ): HostContextStore<K> => {\n if (this.appsSdkStores && (key === \"display\" || key === \"viewState\")) {\n return this.appsSdkStores[\n key as \"display\" | \"viewState\"\n ] as HostContextStore<K>;\n }\n\n if (key === \"display\") {\n return this.polyfillDisplayStore as HostContextStore<K>;\n }\n\n if (key === \"viewState\") {\n return this.polyfillViewStateStore as HostContextStore<K>;\n }\n\n return this.mcpStores[\n key as keyof typeof this.mcpStores\n ] as HostContextStore<K>;\n };\n\n public callTool = async <\n ToolArgs extends Record<string, unknown> | null = null,\n ToolResponse extends CallToolResponse = CallToolResponse,\n >(\n name: string,\n args: ToolArgs,\n ): Promise<ToolResponse> => {\n const app = await this.mcp.getApp();\n const response = await app.callServerTool({\n name,\n arguments: args ?? undefined,\n });\n return {\n content: response.content,\n structuredContent: response.structuredContent ?? {},\n isError: response.isError ?? false,\n meta: response._meta ?? {},\n } as ToolResponse;\n };\n\n public requestDisplayMode = async (mode: RequestDisplayMode) => {\n const app = await this.mcp.getApp();\n return app.requestDisplayMode({ mode });\n };\n\n public requestClose = async (): Promise<void> => {\n const app = await this.mcp.getApp();\n await app.requestTeardown();\n };\n\n public requestSize = async (size: RequestSizeOptions): Promise<void> => {\n const app = await this.mcp.getApp();\n await app.sendSizeChanged(size);\n };\n\n public sendFollowUpMessage = async (\n prompt: string,\n options?: SendFollowUpMessageOptions,\n ): Promise<void> => {\n if (this.openai && options?.scrollToBottom !== undefined) {\n await this.openai.sendFollowUpMessage({\n prompt,\n scrollToBottom: options.scrollToBottom,\n });\n return;\n }\n const app = await this.mcp.getApp();\n await app.sendMessage({\n role: \"user\",\n content: [{ type: \"text\", text: prompt }],\n });\n };\n\n public openExternal = (href: string, options?: OpenExternalOptions): void => {\n if (this.openai && options?.redirectUrl === false) {\n this.openai.openExternal({ href, redirectUrl: false });\n return;\n }\n this.mcp\n .getApp()\n .then((app) => app.openLink({ url: href }))\n .catch((err) => {\n console.error(\"Failed to open external link:\", err);\n });\n };\n\n public download = async (params: DownloadParams): Promise<DownloadResult> => {\n const app = await this.mcp.getApp();\n if (!app.getHostCapabilities()?.downloadFile) {\n console.error(\n \"[skybridge] download: host does not support ui/download-file\",\n );\n return { isError: true };\n }\n return app.downloadFile(params);\n };\n\n public setViewState = async (\n stateOrUpdater: SetViewStateAction,\n ): Promise<void> => {\n if (this.openai) {\n const modelContent =\n typeof stateOrUpdater === \"function\"\n ? stateOrUpdater(this.openai.widgetState?.modelContent ?? null)\n : stateOrUpdater;\n await this.openai.setWidgetState({\n privateContent: {},\n ...this.openai.widgetState,\n modelContent,\n });\n return;\n }\n\n const newState =\n typeof stateOrUpdater === \"function\"\n ? stateOrUpdater(this._viewState)\n : stateOrUpdater;\n\n // update local state immediately so successive calls see fresh state\n this._viewState = newState;\n this.viewStateListeners.forEach((l) => {\n l();\n });\n\n this.persistToLocalStorage(newState);\n\n try {\n const app = await this.mcp.getApp();\n await app.updateModelContext({\n structuredContent: newState,\n content: [{ type: \"text\", text: JSON.stringify(newState) }],\n });\n } catch (error) {\n console.error(\"Failed to update view state in MCP App.\", error);\n }\n };\n\n public uploadFile = async (\n file: File,\n options?: UploadFileOptions,\n ): Promise<FileMetadata> => {\n if (!this.openai) {\n throw new NotSupportedError(\"uploadFile\");\n }\n const metadata = await this.openai.uploadFile(file, options);\n await this.trackFileIds(metadata.fileId);\n return metadata;\n };\n\n public getFileDownloadUrl = (\n file: FileMetadata,\n ): Promise<{ downloadUrl: string }> => {\n if (!this.openai) {\n return Promise.reject(new NotSupportedError(\"getFileDownloadUrl\"));\n }\n return this.openai.getFileDownloadUrl(file);\n };\n\n public selectFiles = async (): Promise<FileMetadata[]> => {\n if (!this.openai) {\n throw new NotSupportedError(\"selectFiles\");\n }\n if (!this.openai.selectFiles) {\n throw new Error(\n \"selectFiles is not supported by the current host version.\",\n );\n }\n const files = await this.openai.selectFiles();\n if (files.length > 0) {\n await this.trackFileIds(...files.map((f) => f.fileId));\n }\n return files;\n };\n\n public openModal = (options: RequestModalOptions): void => {\n if (this.openai) {\n this.openai.requestModal(options);\n return;\n }\n this._polyfillDisplay = { mode: \"modal\", params: options.params };\n this.polyfillDisplayListeners.forEach((l) => {\n l();\n });\n };\n\n public setOpenInAppUrl = (href: string): Promise<void> => {\n if (!this.openai) {\n return Promise.reject(new NotSupportedError(\"setOpenInAppUrl\"));\n }\n const trimmed = href.trim();\n if (!trimmed) {\n return Promise.reject(new Error(\"The href parameter is required.\"));\n }\n return this.openai.setOpenInAppUrl({ href: trimmed });\n };\n\n public closeModal = (): void => {\n if (this.openai) {\n return; // host owns modal lifecycle\n }\n this._polyfillDisplay = { mode: \"inline\" };\n this.polyfillDisplayListeners.forEach((l) => {\n l();\n });\n };\n\n private async trackFileIds(...fileIds: string[]): Promise<void> {\n if (!this.openai) {\n return;\n }\n const current = this.openai.widgetState;\n const state: AppsSdkWidgetState = current\n ? { ...current }\n : { modelContent: {}, privateContent: {} };\n if (!state.imageIds) {\n state.imageIds = [];\n }\n state.imageIds.push(...fileIds);\n await this.openai.setWidgetState(state);\n }\n\n // ---- viewState persistence helpers (used in subsequent tasks) ----\n\n private subscribeToViewUUID(): void {\n this.unsubscribeViewUUID = this.mcp.subscribe(\"toolResult\")(() => {\n const toolResult = this.mcp.getSnapshot(\"toolResult\");\n const viewUUID = (\n toolResult?._meta as Record<string, unknown> | undefined\n )?.viewUUID as string | undefined;\n if (viewUUID && viewUUID !== this._viewUUID) {\n this._viewUUID = viewUUID;\n this.restoreFromLocalStorage(viewUUID);\n }\n });\n }\n\n private restoreFromLocalStorage(viewUUID: string): void {\n try {\n const existingKey = findStorageKey(viewUUID);\n if (existingKey) {\n const stored = localStorage.getItem(existingKey);\n if (stored !== null) {\n this._viewState = JSON.parse(stored);\n this.viewStateListeners.forEach((l) => {\n l();\n });\n }\n }\n } catch (err) {\n console.error(err);\n }\n }\n\n protected persistToLocalStorage(state: Record<string, unknown> | null): void {\n if (!this._viewUUID || state === null) {\n return;\n }\n try {\n const oldKey = findStorageKey(this._viewUUID);\n if (oldKey) {\n localStorage.removeItem(oldKey);\n }\n const newKey = `${STORAGE_PREFIX}${Date.now()}:${this._viewUUID}`;\n localStorage.setItem(newKey, JSON.stringify(state));\n const keys: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key?.startsWith(STORAGE_PREFIX)) {\n keys.push(key);\n }\n }\n if (keys.length <= MAX_STORAGE_ENTRIES) {\n return;\n }\n keys.sort();\n const toRemove = keys.slice(0, keys.length - MAX_STORAGE_ENTRIES);\n for (const key of toRemove) {\n localStorage.removeItem(key);\n }\n } catch (err) {\n console.error(err);\n }\n }\n}\n"]}
@@ -0,0 +1,205 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { HostAdaptor } from "./adaptor.js";
3
+ import { AppsSdkBridge } from "./apps-sdk/bridge.js";
4
+ import { McpAppBridge } from "./mcp-app/bridge.js";
5
+ import { NotSupportedError } from "./types.js";
6
+ describe("HostAdaptor", () => {
7
+ beforeEach(() => {
8
+ McpAppBridge.resetInstance();
9
+ AppsSdkBridge.resetInstance();
10
+ vi.stubGlobal("skybridge", { hostType: "apps-sdk" });
11
+ vi.stubGlobal("parent", { postMessage: vi.fn() });
12
+ localStorage.clear();
13
+ });
14
+ afterEach(() => {
15
+ vi.unstubAllGlobals();
16
+ McpAppBridge.resetInstance();
17
+ AppsSdkBridge.resetInstance();
18
+ localStorage.clear();
19
+ });
20
+ it("captures window.openai as overlay when present, else window.openai is null", () => {
21
+ vi.stubGlobal("openai", undefined);
22
+ expect(new HostAdaptor().hasAppsSdkOverlay()).toBe(false);
23
+ McpAppBridge.resetInstance();
24
+ AppsSdkBridge.resetInstance();
25
+ vi.stubGlobal("openai", { widgetState: null });
26
+ expect(new HostAdaptor().hasAppsSdkOverlay()).toBe(true);
27
+ });
28
+ it("callTool always routes to mcp.callServerTool", async () => {
29
+ vi.stubGlobal("openai", { callTool: vi.fn() });
30
+ const adaptor = new HostAdaptor();
31
+ const fakeApp = {
32
+ callServerTool: vi.fn().mockResolvedValue({
33
+ content: [],
34
+ structuredContent: { ok: true },
35
+ isError: false,
36
+ _meta: { x: 1 },
37
+ }),
38
+ };
39
+ // biome-ignore lint/suspicious/noExplicitAny: test seam
40
+ adaptor.mcp.getApp = vi.fn().mockResolvedValue(fakeApp);
41
+ const result = await adaptor.callTool("greet", { name: "alice" });
42
+ expect(fakeApp.callServerTool).toHaveBeenCalledWith({
43
+ name: "greet",
44
+ arguments: { name: "alice" },
45
+ });
46
+ expect(result.structuredContent).toEqual({ ok: true });
47
+ expect(result.meta).toEqual({ x: 1 });
48
+ });
49
+ it("download returns isError when host lacks downloadFile capability", async () => {
50
+ vi.stubGlobal("openai", undefined);
51
+ const adaptor = new HostAdaptor();
52
+ const fakeApp = {
53
+ getHostCapabilities: () => ({}),
54
+ downloadFile: vi.fn(),
55
+ };
56
+ // biome-ignore lint/suspicious/noExplicitAny: test seam
57
+ adaptor.mcp.getApp = vi.fn().mockResolvedValue(fakeApp);
58
+ const r = await adaptor.download({ contents: [] });
59
+ expect(r.isError).toBe(true);
60
+ expect(fakeApp.downloadFile).not.toHaveBeenCalled();
61
+ });
62
+ it("sendFollowUpMessage routes to window.openai only when scrollToBottom is set", async () => {
63
+ const sendFollowUpMessage = vi.fn().mockResolvedValue(undefined);
64
+ vi.stubGlobal("openai", { sendFollowUpMessage });
65
+ let adaptor = new HostAdaptor();
66
+ await adaptor.sendFollowUpMessage("hi", { scrollToBottom: false });
67
+ expect(sendFollowUpMessage).toHaveBeenCalledWith({
68
+ prompt: "hi",
69
+ scrollToBottom: false,
70
+ });
71
+ // Default case falls through to MCP
72
+ McpAppBridge.resetInstance();
73
+ AppsSdkBridge.resetInstance();
74
+ vi.stubGlobal("openai", undefined);
75
+ adaptor = new HostAdaptor();
76
+ const sendMessage = vi.fn().mockResolvedValue(undefined);
77
+ // biome-ignore lint/suspicious/noExplicitAny: test seam
78
+ adaptor.mcp.getApp = vi.fn().mockResolvedValue({ sendMessage });
79
+ await adaptor.sendFollowUpMessage("hi");
80
+ expect(sendMessage).toHaveBeenCalledWith({
81
+ role: "user",
82
+ content: [{ type: "text", text: "hi" }],
83
+ });
84
+ });
85
+ it("openExternal routes to window.openai only when redirectUrl: false", async () => {
86
+ const openExternal = vi.fn();
87
+ vi.stubGlobal("openai", { openExternal });
88
+ let adaptor = new HostAdaptor();
89
+ adaptor.openExternal("https://x", { redirectUrl: false });
90
+ expect(openExternal).toHaveBeenCalledWith({
91
+ href: "https://x",
92
+ redirectUrl: false,
93
+ });
94
+ // Default case falls through to MCP openLink
95
+ McpAppBridge.resetInstance();
96
+ AppsSdkBridge.resetInstance();
97
+ vi.stubGlobal("openai", undefined);
98
+ adaptor = new HostAdaptor();
99
+ const openLink = vi.fn();
100
+ // biome-ignore lint/suspicious/noExplicitAny: test seam
101
+ adaptor.mcp.getApp = vi.fn().mockResolvedValue({ openLink });
102
+ adaptor.openExternal("https://x");
103
+ await new Promise((r) => setTimeout(r, 0));
104
+ expect(openLink).toHaveBeenCalledWith({ url: "https://x" });
105
+ });
106
+ it("Apps-SDK-exclusive methods throw NotSupportedError when window.openai is null", async () => {
107
+ vi.stubGlobal("openai", undefined);
108
+ const adaptor = new HostAdaptor();
109
+ await expect(adaptor.uploadFile(new File([], "x"))).rejects.toThrow(NotSupportedError);
110
+ await expect(adaptor.selectFiles()).rejects.toThrow(NotSupportedError);
111
+ await expect(adaptor.getFileDownloadUrl({ fileId: "x" })).rejects.toThrow(NotSupportedError);
112
+ await expect(adaptor.setOpenInAppUrl("https://x")).rejects.toThrow(NotSupportedError);
113
+ });
114
+ it("uploadFile delegates to window.openai.uploadFile and tracks fileId in widgetState", async () => {
115
+ const setWidgetState = vi.fn().mockResolvedValue(undefined);
116
+ const uploadFile = vi
117
+ .fn()
118
+ .mockResolvedValue({ fileId: "abc", fileName: "x" });
119
+ vi.stubGlobal("openai", { uploadFile, setWidgetState, widgetState: null });
120
+ const adaptor = new HostAdaptor();
121
+ const file = new File([], "x");
122
+ const r = await adaptor.uploadFile(file, { library: true });
123
+ expect(uploadFile).toHaveBeenCalledWith(file, { library: true });
124
+ expect(r.fileId).toBe("abc");
125
+ expect(setWidgetState).toHaveBeenCalledWith(expect.objectContaining({ imageIds: ["abc"] }));
126
+ });
127
+ it("setViewState uses window.openai.setWidgetState when present", async () => {
128
+ const setWidgetState = vi.fn().mockResolvedValue(undefined);
129
+ vi.stubGlobal("openai", { setWidgetState, widgetState: null });
130
+ const adaptor = new HostAdaptor();
131
+ await adaptor.setViewState({ count: 1 });
132
+ expect(setWidgetState).toHaveBeenCalledWith(expect.objectContaining({ modelContent: { count: 1 } }));
133
+ });
134
+ it("setViewState falls back to MCP updateModelContext + localStorage when window.openai is absent", async () => {
135
+ vi.stubGlobal("openai", undefined);
136
+ const adaptor = new HostAdaptor();
137
+ const updateModelContext = vi.fn().mockResolvedValue(undefined);
138
+ // biome-ignore lint/suspicious/noExplicitAny: test seam
139
+ adaptor.mcp.getApp = vi
140
+ .fn()
141
+ .mockResolvedValue({ updateModelContext });
142
+ // biome-ignore lint/suspicious/noExplicitAny: test seam
143
+ adaptor._viewUUID = "view-1";
144
+ await adaptor.setViewState({ count: 2 });
145
+ expect(updateModelContext).toHaveBeenCalledWith({
146
+ structuredContent: { count: 2 },
147
+ content: [{ type: "text", text: JSON.stringify({ count: 2 }) }],
148
+ });
149
+ const keys = [];
150
+ for (let i = 0; i < localStorage.length; i++) {
151
+ const key = localStorage.key(i);
152
+ if (key !== null) {
153
+ keys.push(key);
154
+ }
155
+ }
156
+ expect(keys.some((k) => k.endsWith(":view-1"))).toBe(true);
157
+ });
158
+ it("openModal/closeModal: delegates to window.openai.requestModal when present, polyfill state otherwise", () => {
159
+ const requestModal = vi.fn();
160
+ vi.stubGlobal("openai", { requestModal });
161
+ let adaptor = new HostAdaptor();
162
+ adaptor.openModal({ title: "Hi" });
163
+ expect(requestModal).toHaveBeenCalledWith({ title: "Hi" });
164
+ expect(() => adaptor.closeModal()).not.toThrow();
165
+ // Polyfill path
166
+ McpAppBridge.resetInstance();
167
+ AppsSdkBridge.resetInstance();
168
+ vi.stubGlobal("openai", undefined);
169
+ adaptor = new HostAdaptor();
170
+ adaptor.openModal({ params: { x: 1 } });
171
+ // biome-ignore lint/suspicious/noExplicitAny: test seam
172
+ expect(adaptor._polyfillDisplay).toEqual({
173
+ mode: "modal",
174
+ params: { x: 1 },
175
+ });
176
+ adaptor.closeModal();
177
+ // biome-ignore lint/suspicious/noExplicitAny: test seam
178
+ expect(adaptor._polyfillDisplay).toEqual({ mode: "inline" });
179
+ });
180
+ it("getHostContextStore: display/viewState route to Apps SDK overlay when present, MCP otherwise", () => {
181
+ vi.stubGlobal("openai", {
182
+ view: { mode: "fullscreen" },
183
+ widgetState: { modelContent: { count: 5 }, privateContent: {} },
184
+ });
185
+ let adaptor = new HostAdaptor();
186
+ expect(adaptor.getHostContextStore("display").getSnapshot()).toEqual({
187
+ mode: "fullscreen",
188
+ });
189
+ expect(adaptor.getHostContextStore("viewState").getSnapshot()).toEqual({
190
+ count: 5,
191
+ });
192
+ // Other keys (theme) always flow through MCP; default fallback when no notification
193
+ expect(adaptor.getHostContextStore("theme").getSnapshot()).toBe("light");
194
+ // Polyfill / local-state path
195
+ McpAppBridge.resetInstance();
196
+ AppsSdkBridge.resetInstance();
197
+ vi.stubGlobal("openai", undefined);
198
+ adaptor = new HostAdaptor();
199
+ expect(adaptor.getHostContextStore("display").getSnapshot()).toEqual({
200
+ mode: "inline",
201
+ });
202
+ expect(adaptor.getHostContextStore("viewState").getSnapshot()).toBe(null);
203
+ });
204
+ });
205
+ //# sourceMappingURL=adaptor.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adaptor.test.js","sourceRoot":"","sources":["../../../src/web/bridges/adaptor.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport { HostAdaptor } from \"./adaptor.js\";\nimport { AppsSdkBridge } from \"./apps-sdk/bridge.js\";\nimport { McpAppBridge } from \"./mcp-app/bridge.js\";\nimport { NotSupportedError } from \"./types.js\";\n\ndescribe(\"HostAdaptor\", () => {\n beforeEach(() => {\n McpAppBridge.resetInstance();\n AppsSdkBridge.resetInstance();\n vi.stubGlobal(\"skybridge\", { hostType: \"apps-sdk\" });\n vi.stubGlobal(\"parent\", { postMessage: vi.fn() });\n localStorage.clear();\n });\n afterEach(() => {\n vi.unstubAllGlobals();\n McpAppBridge.resetInstance();\n AppsSdkBridge.resetInstance();\n localStorage.clear();\n });\n\n it(\"captures window.openai as overlay when present, else window.openai is null\", () => {\n vi.stubGlobal(\"openai\", undefined);\n expect(new HostAdaptor().hasAppsSdkOverlay()).toBe(false);\n McpAppBridge.resetInstance();\n AppsSdkBridge.resetInstance();\n vi.stubGlobal(\"openai\", { widgetState: null });\n expect(new HostAdaptor().hasAppsSdkOverlay()).toBe(true);\n });\n\n it(\"callTool always routes to mcp.callServerTool\", async () => {\n vi.stubGlobal(\"openai\", { callTool: vi.fn() });\n const adaptor = new HostAdaptor();\n const fakeApp = {\n callServerTool: vi.fn().mockResolvedValue({\n content: [],\n structuredContent: { ok: true },\n isError: false,\n _meta: { x: 1 },\n }),\n };\n // biome-ignore lint/suspicious/noExplicitAny: test seam\n (adaptor as any).mcp.getApp = vi.fn().mockResolvedValue(fakeApp);\n\n const result = await adaptor.callTool(\"greet\", { name: \"alice\" });\n expect(fakeApp.callServerTool).toHaveBeenCalledWith({\n name: \"greet\",\n arguments: { name: \"alice\" },\n });\n expect(result.structuredContent).toEqual({ ok: true });\n expect(result.meta).toEqual({ x: 1 });\n });\n\n it(\"download returns isError when host lacks downloadFile capability\", async () => {\n vi.stubGlobal(\"openai\", undefined);\n const adaptor = new HostAdaptor();\n const fakeApp = {\n getHostCapabilities: () => ({}),\n downloadFile: vi.fn(),\n };\n // biome-ignore lint/suspicious/noExplicitAny: test seam\n (adaptor as any).mcp.getApp = vi.fn().mockResolvedValue(fakeApp);\n const r = await adaptor.download({ contents: [] });\n expect(r.isError).toBe(true);\n expect(fakeApp.downloadFile).not.toHaveBeenCalled();\n });\n\n it(\"sendFollowUpMessage routes to window.openai only when scrollToBottom is set\", async () => {\n const sendFollowUpMessage = vi.fn().mockResolvedValue(undefined);\n vi.stubGlobal(\"openai\", { sendFollowUpMessage });\n let adaptor = new HostAdaptor();\n await adaptor.sendFollowUpMessage(\"hi\", { scrollToBottom: false });\n expect(sendFollowUpMessage).toHaveBeenCalledWith({\n prompt: \"hi\",\n scrollToBottom: false,\n });\n\n // Default case falls through to MCP\n McpAppBridge.resetInstance();\n AppsSdkBridge.resetInstance();\n vi.stubGlobal(\"openai\", undefined);\n adaptor = new HostAdaptor();\n const sendMessage = vi.fn().mockResolvedValue(undefined);\n // biome-ignore lint/suspicious/noExplicitAny: test seam\n (adaptor as any).mcp.getApp = vi.fn().mockResolvedValue({ sendMessage });\n await adaptor.sendFollowUpMessage(\"hi\");\n expect(sendMessage).toHaveBeenCalledWith({\n role: \"user\",\n content: [{ type: \"text\", text: \"hi\" }],\n });\n });\n\n it(\"openExternal routes to window.openai only when redirectUrl: false\", async () => {\n const openExternal = vi.fn();\n vi.stubGlobal(\"openai\", { openExternal });\n let adaptor = new HostAdaptor();\n adaptor.openExternal(\"https://x\", { redirectUrl: false });\n expect(openExternal).toHaveBeenCalledWith({\n href: \"https://x\",\n redirectUrl: false,\n });\n\n // Default case falls through to MCP openLink\n McpAppBridge.resetInstance();\n AppsSdkBridge.resetInstance();\n vi.stubGlobal(\"openai\", undefined);\n adaptor = new HostAdaptor();\n const openLink = vi.fn();\n // biome-ignore lint/suspicious/noExplicitAny: test seam\n (adaptor as any).mcp.getApp = vi.fn().mockResolvedValue({ openLink });\n adaptor.openExternal(\"https://x\");\n await new Promise((r) => setTimeout(r, 0));\n expect(openLink).toHaveBeenCalledWith({ url: \"https://x\" });\n });\n\n it(\"Apps-SDK-exclusive methods throw NotSupportedError when window.openai is null\", async () => {\n vi.stubGlobal(\"openai\", undefined);\n const adaptor = new HostAdaptor();\n await expect(adaptor.uploadFile(new File([], \"x\"))).rejects.toThrow(\n NotSupportedError,\n );\n await expect(adaptor.selectFiles()).rejects.toThrow(NotSupportedError);\n await expect(adaptor.getFileDownloadUrl({ fileId: \"x\" })).rejects.toThrow(\n NotSupportedError,\n );\n await expect(adaptor.setOpenInAppUrl(\"https://x\")).rejects.toThrow(\n NotSupportedError,\n );\n });\n\n it(\"uploadFile delegates to window.openai.uploadFile and tracks fileId in widgetState\", async () => {\n const setWidgetState = vi.fn().mockResolvedValue(undefined);\n const uploadFile = vi\n .fn()\n .mockResolvedValue({ fileId: \"abc\", fileName: \"x\" });\n vi.stubGlobal(\"openai\", { uploadFile, setWidgetState, widgetState: null });\n const adaptor = new HostAdaptor();\n const file = new File([], \"x\");\n const r = await adaptor.uploadFile(file, { library: true });\n expect(uploadFile).toHaveBeenCalledWith(file, { library: true });\n expect(r.fileId).toBe(\"abc\");\n expect(setWidgetState).toHaveBeenCalledWith(\n expect.objectContaining({ imageIds: [\"abc\"] }),\n );\n });\n\n it(\"setViewState uses window.openai.setWidgetState when present\", async () => {\n const setWidgetState = vi.fn().mockResolvedValue(undefined);\n vi.stubGlobal(\"openai\", { setWidgetState, widgetState: null });\n const adaptor = new HostAdaptor();\n await adaptor.setViewState({ count: 1 });\n expect(setWidgetState).toHaveBeenCalledWith(\n expect.objectContaining({ modelContent: { count: 1 } }),\n );\n });\n\n it(\"setViewState falls back to MCP updateModelContext + localStorage when window.openai is absent\", async () => {\n vi.stubGlobal(\"openai\", undefined);\n const adaptor = new HostAdaptor();\n const updateModelContext = vi.fn().mockResolvedValue(undefined);\n // biome-ignore lint/suspicious/noExplicitAny: test seam\n (adaptor as any).mcp.getApp = vi\n .fn()\n .mockResolvedValue({ updateModelContext });\n // biome-ignore lint/suspicious/noExplicitAny: test seam\n (adaptor as any)._viewUUID = \"view-1\";\n await adaptor.setViewState({ count: 2 });\n expect(updateModelContext).toHaveBeenCalledWith({\n structuredContent: { count: 2 },\n content: [{ type: \"text\", text: JSON.stringify({ count: 2 }) }],\n });\n const keys: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key !== null) {\n keys.push(key);\n }\n }\n expect(keys.some((k) => k.endsWith(\":view-1\"))).toBe(true);\n });\n\n it(\"openModal/closeModal: delegates to window.openai.requestModal when present, polyfill state otherwise\", () => {\n const requestModal = vi.fn();\n vi.stubGlobal(\"openai\", { requestModal });\n let adaptor = new HostAdaptor();\n adaptor.openModal({ title: \"Hi\" });\n expect(requestModal).toHaveBeenCalledWith({ title: \"Hi\" });\n expect(() => adaptor.closeModal()).not.toThrow();\n\n // Polyfill path\n McpAppBridge.resetInstance();\n AppsSdkBridge.resetInstance();\n vi.stubGlobal(\"openai\", undefined);\n adaptor = new HostAdaptor();\n adaptor.openModal({ params: { x: 1 } });\n // biome-ignore lint/suspicious/noExplicitAny: test seam\n expect((adaptor as any)._polyfillDisplay).toEqual({\n mode: \"modal\",\n params: { x: 1 },\n });\n adaptor.closeModal();\n // biome-ignore lint/suspicious/noExplicitAny: test seam\n expect((adaptor as any)._polyfillDisplay).toEqual({ mode: \"inline\" });\n });\n\n it(\"getHostContextStore: display/viewState route to Apps SDK overlay when present, MCP otherwise\", () => {\n vi.stubGlobal(\"openai\", {\n view: { mode: \"fullscreen\" },\n widgetState: { modelContent: { count: 5 }, privateContent: {} },\n });\n let adaptor = new HostAdaptor();\n expect(adaptor.getHostContextStore(\"display\").getSnapshot()).toEqual({\n mode: \"fullscreen\",\n });\n expect(adaptor.getHostContextStore(\"viewState\").getSnapshot()).toEqual({\n count: 5,\n });\n // Other keys (theme) always flow through MCP; default fallback when no notification\n expect(adaptor.getHostContextStore(\"theme\").getSnapshot()).toBe(\"light\");\n\n // Polyfill / local-state path\n McpAppBridge.resetInstance();\n AppsSdkBridge.resetInstance();\n vi.stubGlobal(\"openai\", undefined);\n adaptor = new HostAdaptor();\n expect(adaptor.getHostContextStore(\"display\").getSnapshot()).toEqual({\n mode: \"inline\",\n });\n expect(adaptor.getHostContextStore(\"viewState\").getSnapshot()).toBe(null);\n });\n});\n"]}
@@ -1,6 +1,6 @@
1
1
  import type { Bridge, Subscribe } from "../types.js";
2
2
  import { type AppsSdkContext } from "./types.js";
3
- /** @internal Singleton bridge over `window.openai` events. Used by {@link AppsSdkAdaptor}. */
3
+ /** @internal Singleton bridge over `window.openai` events. Used by `HostAdaptor`. */
4
4
  export declare class AppsSdkBridge implements Bridge<AppsSdkContext> {
5
5
  private static instance;
6
6
  static getInstance(): AppsSdkBridge;
@@ -1,11 +1,10 @@
1
1
  import { SET_GLOBALS_EVENT_TYPE, } from "./types.js";
2
- /** @internal Singleton bridge over `window.openai` events. Used by {@link AppsSdkAdaptor}. */
2
+ /** @internal Singleton bridge over `window.openai` events. Used by `HostAdaptor`. */
3
3
  export class AppsSdkBridge {
4
4
  static instance = null;
5
5
  static getInstance() {
6
- if (window.skybridge.hostType !== "apps-sdk" ||
7
- window.openai === undefined) {
8
- throw new Error("Apps SDK Bridge can only be used in the apps-sdk runtime");
6
+ if (window.openai === undefined) {
7
+ throw new Error("Apps SDK Bridge requires window.openai (Apps SDK runtime).");
9
8
  }
10
9
  if (AppsSdkBridge.instance === null) {
11
10
  AppsSdkBridge.instance = new AppsSdkBridge();