@portel/photon 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +361 -339
  2. package/dist/auto-ui/beam.d.ts +5 -0
  3. package/dist/auto-ui/beam.d.ts.map +1 -1
  4. package/dist/auto-ui/beam.js +727 -51
  5. package/dist/auto-ui/beam.js.map +1 -1
  6. package/dist/auto-ui/bridge/index.d.ts +37 -0
  7. package/dist/auto-ui/bridge/index.d.ts.map +1 -0
  8. package/dist/auto-ui/bridge/index.js +555 -0
  9. package/dist/auto-ui/bridge/index.js.map +1 -0
  10. package/dist/auto-ui/bridge/openai-shim.d.ts +20 -0
  11. package/dist/auto-ui/bridge/openai-shim.d.ts.map +1 -0
  12. package/dist/auto-ui/bridge/openai-shim.js +231 -0
  13. package/dist/auto-ui/bridge/openai-shim.js.map +1 -0
  14. package/dist/auto-ui/bridge/photon-app.d.ts +162 -0
  15. package/dist/auto-ui/bridge/photon-app.d.ts.map +1 -0
  16. package/dist/auto-ui/bridge/photon-app.js +460 -0
  17. package/dist/auto-ui/bridge/photon-app.js.map +1 -0
  18. package/dist/auto-ui/bridge/types.d.ts +128 -0
  19. package/dist/auto-ui/bridge/types.d.ts.map +1 -0
  20. package/dist/auto-ui/bridge/types.js +7 -0
  21. package/dist/auto-ui/bridge/types.js.map +1 -0
  22. package/dist/auto-ui/design-system/tokens.d.ts +1 -1
  23. package/dist/auto-ui/design-system/tokens.d.ts.map +1 -1
  24. package/dist/auto-ui/design-system/tokens.js +1 -1
  25. package/dist/auto-ui/design-system/tokens.js.map +1 -1
  26. package/dist/auto-ui/index.d.ts +3 -1
  27. package/dist/auto-ui/index.d.ts.map +1 -1
  28. package/dist/auto-ui/index.js +5 -2
  29. package/dist/auto-ui/index.js.map +1 -1
  30. package/dist/auto-ui/platform-compat.d.ts.map +1 -1
  31. package/dist/auto-ui/platform-compat.js +60 -6
  32. package/dist/auto-ui/platform-compat.js.map +1 -1
  33. package/dist/auto-ui/streamable-http-transport.d.ts +25 -1
  34. package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -1
  35. package/dist/auto-ui/streamable-http-transport.js +581 -20
  36. package/dist/auto-ui/streamable-http-transport.js.map +1 -1
  37. package/dist/auto-ui/types.d.ts +74 -0
  38. package/dist/auto-ui/types.d.ts.map +1 -1
  39. package/dist/auto-ui/types.js +21 -0
  40. package/dist/auto-ui/types.js.map +1 -1
  41. package/dist/beam.bundle.js +51422 -1791
  42. package/dist/beam.bundle.js.map +4 -4
  43. package/dist/cli.js +12 -2
  44. package/dist/cli.js.map +1 -1
  45. package/dist/daemon/client.d.ts +5 -3
  46. package/dist/daemon/client.d.ts.map +1 -1
  47. package/dist/daemon/client.js +30 -4
  48. package/dist/daemon/client.js.map +1 -1
  49. package/dist/daemon/manager.d.ts +5 -0
  50. package/dist/daemon/manager.d.ts.map +1 -1
  51. package/dist/daemon/manager.js +20 -0
  52. package/dist/daemon/manager.js.map +1 -1
  53. package/dist/loader.d.ts +23 -0
  54. package/dist/loader.d.ts.map +1 -1
  55. package/dist/loader.js +77 -12
  56. package/dist/loader.js.map +1 -1
  57. package/dist/photon-cli-runner.d.ts.map +1 -1
  58. package/dist/photon-cli-runner.js +2 -0
  59. package/dist/photon-cli-runner.js.map +1 -1
  60. package/dist/photon-doc-extractor.d.ts +1 -0
  61. package/dist/photon-doc-extractor.d.ts.map +1 -1
  62. package/dist/photon-doc-extractor.js +25 -6
  63. package/dist/photon-doc-extractor.js.map +1 -1
  64. package/dist/server.d.ts +12 -1
  65. package/dist/server.d.ts.map +1 -1
  66. package/dist/server.js +386 -13
  67. package/dist/server.js.map +1 -1
  68. package/dist/template-manager.js +2 -2
  69. package/dist/version.d.ts +8 -0
  70. package/dist/version.d.ts.map +1 -1
  71. package/dist/version.js +16 -0
  72. package/dist/version.js.map +1 -1
  73. package/package.json +19 -9
@@ -0,0 +1,555 @@
1
+ import { getThemeTokens } from '../design-system/tokens.js';
2
+ // Note: PhotonApp and createOpenAIShim are browser-only code
3
+ // They are embedded in the generated bridge script, not exported for server use
4
+ /**
5
+ * Generate the complete bridge script for injection into iframes
6
+ *
7
+ * This script provides:
8
+ * - window.photon API (PhotonApp-based)
9
+ * - window.openai API (compatibility shim)
10
+ * - Automatic theme application
11
+ * - MCP Apps protocol handshake
12
+ */
13
+ export function generateBridgeScript(context) {
14
+ // Get theme tokens for the specified theme (light or dark)
15
+ const themeTokens = getThemeTokens(context.theme);
16
+ const themeTokensJson = JSON.stringify(themeTokens);
17
+ const contextJson = JSON.stringify(context);
18
+ return `
19
+ <script>
20
+ (function() {
21
+ 'use strict';
22
+
23
+ // ═══════════════════════════════════════════════════════════════════════════
24
+ // SHARED STATE
25
+ // ═══════════════════════════════════════════════════════════════════════════
26
+
27
+ var ctx = ${contextJson};
28
+ var themeTokens = ${themeTokensJson};
29
+ var toolInput = {};
30
+ var toolOutput = null;
31
+ var widgetState = {};
32
+ var hostContext = null;
33
+ var pendingCalls = {};
34
+ var callIdCounter = 0;
35
+
36
+ // Event listeners
37
+ var listeners = {
38
+ result: [],
39
+ themeChange: [],
40
+ progress: [],
41
+ status: [],
42
+ stream: [],
43
+ emit: []
44
+ };
45
+ var eventListeners = {}; // For specific event subscriptions (e.g., 'taskMove')
46
+ var photonEventListeners = {}; // Namespaced by photon name for injected photons
47
+ var injectedPhotons = ctx.injectedPhotons || [];
48
+
49
+ function subscribe(arr, cb) {
50
+ arr.push(cb);
51
+ return function() {
52
+ var i = arr.indexOf(cb);
53
+ if (i >= 0) arr.splice(i, 1);
54
+ };
55
+ }
56
+
57
+ function postToHost(msg) {
58
+ window.parent.postMessage(msg, '*');
59
+ }
60
+
61
+ function generateCallId() {
62
+ return 'call_' + (++callIdCounter) + '_' + Math.random().toString(36).slice(2);
63
+ }
64
+
65
+ // ═══════════════════════════════════════════════════════════════════════════
66
+ // DATA EXTRACTION (MCP result format -> clean data)
67
+ // ═══════════════════════════════════════════════════════════════════════════
68
+
69
+ function extractData(result) {
70
+ if (!result) return result;
71
+ if (result.structuredContent) return result.structuredContent;
72
+ if (Array.isArray(result.content)) {
73
+ var textItem = result.content.find(function(item) { return item.type === 'text'; });
74
+ if (textItem && textItem.text) {
75
+ try { return JSON.parse(textItem.text); } catch (e) { return textItem.text; }
76
+ }
77
+ }
78
+ return result;
79
+ }
80
+
81
+ // ═══════════════════════════════════════════════════════════════════════════
82
+ // MESSAGE HANDLER
83
+ // ═══════════════════════════════════════════════════════════════════════════
84
+
85
+ window.addEventListener('message', function(e) {
86
+ var m = e.data;
87
+ if (!m || typeof m !== 'object') return;
88
+
89
+ // ─────────────────────────────────────────────────────────────────────────
90
+ // JSON-RPC 2.0 (MCP Apps Extension protocol)
91
+ // ─────────────────────────────────────────────────────────────────────────
92
+ if (m.jsonrpc === '2.0') {
93
+ // Response to our call (has id, no method)
94
+ if (m.id && !m.method) {
95
+ var pending = pendingCalls[m.id];
96
+ if (pending) {
97
+ delete pendingCalls[m.id];
98
+ if (m.error) {
99
+ pending.reject(new Error(m.error.message || 'Call failed'));
100
+ } else if (m.result && m.result.isError) {
101
+ var errorData = extractData(m.result);
102
+ var errorMsg = typeof errorData === 'string' ? errorData : JSON.stringify(errorData);
103
+ pending.reject(new Error(errorMsg || 'Tool returned an error'));
104
+ } else {
105
+ pending.resolve(extractData(m.result));
106
+ }
107
+ }
108
+ return;
109
+ }
110
+
111
+ // Handle notifications and requests from host
112
+ if (m.method === 'ui/initialize') {
113
+ var params = m.params || {};
114
+ hostContext = params.hostContext;
115
+ if (params.hostContext) {
116
+ if (params.hostContext.theme) ctx.theme = params.hostContext.theme;
117
+ if (params.hostContext.styles && params.hostContext.styles.variables) {
118
+ themeTokens = params.hostContext.styles.variables;
119
+ }
120
+ }
121
+ applyThemeTokens();
122
+ // Send initialized notification
123
+ postToHost({ jsonrpc: '2.0', method: 'ui/notifications/initialized', params: {} });
124
+ }
125
+ else if (m.method === 'ui/notifications/tool-result') {
126
+ toolOutput = extractData(m.params && m.params.result);
127
+ listeners.result.forEach(function(cb) { cb(toolOutput); });
128
+ }
129
+ else if (m.method === 'ui/notifications/tool-input') {
130
+ toolInput = (m.params && m.params.input) || {};
131
+ }
132
+ else if (m.method === 'ui/notifications/host-context-changed' || m.method === 'ui/notifications/context') {
133
+ var ctxParams = m.params || {};
134
+ // Standard theme/styles handling
135
+ if (ctxParams.styles && ctxParams.styles.variables) {
136
+ themeTokens = ctxParams.styles.variables;
137
+ applyThemeTokens();
138
+ }
139
+ if (ctxParams.theme) {
140
+ ctx.theme = ctxParams.theme;
141
+ applyThemeClass();
142
+ listeners.themeChange.forEach(function(cb) { cb(ctx.theme); });
143
+ }
144
+
145
+ // Extract embedded photon event data (for real-time sync)
146
+ if (ctxParams._photon) {
147
+ var photonData = ctxParams._photon;
148
+ // Route to generic emit listeners
149
+ listeners.emit.forEach(function(cb) { cb(photonData); });
150
+
151
+ var eventName = photonData.event;
152
+ var sourcePhoton = photonData.data && photonData.data._source;
153
+
154
+ // Route to photon-specific listeners if _source is specified (injected photon events)
155
+ if (sourcePhoton && photonEventListeners[sourcePhoton] && photonEventListeners[sourcePhoton][eventName]) {
156
+ photonEventListeners[sourcePhoton][eventName].forEach(function(cb) {
157
+ cb(photonData.data);
158
+ });
159
+ }
160
+
161
+ // Also route to global event listeners (main photon events, or fallback)
162
+ if (eventName && eventListeners[eventName]) {
163
+ eventListeners[eventName].forEach(function(cb) {
164
+ cb(photonData.data);
165
+ });
166
+ }
167
+ }
168
+ }
169
+ else if (m.method === 'ui/resource-teardown' && m.id != null) {
170
+ postToHost({ jsonrpc: '2.0', id: m.id, result: {} });
171
+ }
172
+ // Custom Photon notifications
173
+ else if (m.method === 'photon/notifications/progress') {
174
+ listeners.progress.forEach(function(cb) { cb(m.params); });
175
+ }
176
+ else if (m.method === 'photon/notifications/status') {
177
+ listeners.status.forEach(function(cb) { cb(m.params); });
178
+ }
179
+ else if (m.method === 'photon/notifications/stream') {
180
+ listeners.stream.forEach(function(cb) { cb(m.params); });
181
+ }
182
+ else if (m.method === 'photon/notifications/emit') {
183
+ listeners.emit.forEach(function(cb) { cb(m.params); });
184
+ }
185
+ return;
186
+ }
187
+
188
+ // ─────────────────────────────────────────────────────────────────────────
189
+ // Legacy photon: messages (backward compatibility)
190
+ // ─────────────────────────────────────────────────────────────────────────
191
+ if (typeof m.type === 'string' && m.type.indexOf('photon:') === 0) {
192
+ if (m.type === 'photon:result') {
193
+ toolOutput = m.data;
194
+ listeners.result.forEach(function(cb) { cb(m.data); });
195
+ }
196
+ else if (m.type === 'photon:theme-change') {
197
+ ctx.theme = m.theme || 'dark';
198
+ if (m.themeTokens) themeTokens = m.themeTokens;
199
+ applyThemeTokens();
200
+ applyThemeClass();
201
+ listeners.themeChange.forEach(function(cb) { cb(ctx.theme); });
202
+ }
203
+ else if (m.type === 'photon:context') {
204
+ Object.assign(ctx, m.context);
205
+ if (m.themeTokens) {
206
+ themeTokens = m.themeTokens;
207
+ applyThemeTokens();
208
+ }
209
+ if (m.context && m.context.theme) {
210
+ applyThemeClass();
211
+ listeners.themeChange.forEach(function(cb) { cb(ctx.theme); });
212
+ }
213
+ }
214
+ else if (m.type === 'photon:emit') {
215
+ var ev = m.event;
216
+ listeners.emit.forEach(function(cb) { cb(ev); });
217
+ if (ev.emit === 'progress') listeners.progress.forEach(function(cb) { cb(ev); });
218
+ else if (ev.emit === 'status') listeners.status.forEach(function(cb) { cb(ev); });
219
+ else if (ev.emit === 'stream') listeners.stream.forEach(function(cb) { cb(ev); });
220
+ }
221
+ }
222
+ });
223
+
224
+ // ═══════════════════════════════════════════════════════════════════════════
225
+ // THEME APPLICATION
226
+ // ═══════════════════════════════════════════════════════════════════════════
227
+
228
+ function applyThemeTokens() {
229
+ var root = document.documentElement;
230
+ for (var key in themeTokens) {
231
+ root.style.setProperty(key, themeTokens[key]);
232
+ }
233
+ applyThemeClass();
234
+ }
235
+
236
+ function applyThemeClass() {
237
+ document.documentElement.classList.remove('light', 'dark', 'light-theme');
238
+ document.documentElement.classList.add(ctx.theme);
239
+ document.documentElement.style.colorScheme = ctx.theme;
240
+ document.documentElement.setAttribute('data-theme', ctx.theme);
241
+
242
+ var lightBg = '#ffffff';
243
+ var lightText = '#1a1a1a';
244
+ var darkBg = '#0d0d0d';
245
+ var darkText = '#e6e6e6';
246
+
247
+ if (ctx.theme === 'light') {
248
+ document.documentElement.classList.add('light-theme');
249
+ document.documentElement.style.backgroundColor = lightBg;
250
+ if (document.body) {
251
+ document.body.style.backgroundColor = lightBg;
252
+ document.body.style.color = lightText;
253
+ }
254
+ } else {
255
+ document.documentElement.style.backgroundColor = darkBg;
256
+ if (document.body) {
257
+ document.body.style.backgroundColor = darkBg;
258
+ document.body.style.color = darkText;
259
+ }
260
+ }
261
+ }
262
+
263
+ // ═══════════════════════════════════════════════════════════════════════════
264
+ // TOOL CALLING
265
+ // ═══════════════════════════════════════════════════════════════════════════
266
+
267
+ function callTool(name, args) {
268
+ var callId = generateCallId();
269
+ return new Promise(function(resolve, reject) {
270
+ pendingCalls[callId] = { resolve: resolve, reject: reject };
271
+ postToHost({
272
+ jsonrpc: '2.0',
273
+ id: callId,
274
+ method: 'tools/call',
275
+ params: { name: name, arguments: args || {} }
276
+ });
277
+ setTimeout(function() {
278
+ if (pendingCalls[callId]) {
279
+ delete pendingCalls[callId];
280
+ reject(new Error('Tool call timeout'));
281
+ }
282
+ }, 30000);
283
+ });
284
+ }
285
+
286
+ // ═══════════════════════════════════════════════════════════════════════════
287
+ // SIZE HANDLING
288
+ // ═══════════════════════════════════════════════════════════════════════════
289
+
290
+ function parseSizeMeta() {
291
+ var meta = document.querySelector('meta[name="mcp:ui-size"]');
292
+ if (!meta) return {};
293
+ var content = meta.getAttribute('content') || '';
294
+ var constraints = {};
295
+ var pairs = content.split(';');
296
+ for (var i = 0; i < pairs.length; i++) {
297
+ var kv = pairs[i].split('=');
298
+ if (kv[0] && kv[1]) {
299
+ var num = parseInt(kv[1], 10);
300
+ if (!isNaN(num)) constraints[kv[0]] = num;
301
+ }
302
+ }
303
+ return constraints;
304
+ }
305
+
306
+ function sendSizeChanged(size) {
307
+ postToHost({
308
+ jsonrpc: '2.0',
309
+ method: 'ui/notifications/size-changed',
310
+ params: size
311
+ });
312
+ }
313
+
314
+ function setupAutoResize(element) {
315
+ var target = element || document.body;
316
+ var constraints = parseSizeMeta();
317
+ var hostMax = hostContext && hostContext.containerDimensions;
318
+
319
+ var observer = new ResizeObserver(function() {
320
+ var width = target.scrollWidth;
321
+ var height = target.scrollHeight;
322
+
323
+ if (constraints.minWidth) width = Math.max(width, constraints.minWidth);
324
+ if (constraints.minHeight) height = Math.max(height, constraints.minHeight);
325
+ if (hostMax && hostMax.maxWidth) width = Math.min(width, hostMax.maxWidth);
326
+ if (hostMax && hostMax.maxHeight) height = Math.min(height, hostMax.maxHeight);
327
+ if (constraints.maxWidth) width = Math.min(width, constraints.maxWidth);
328
+ if (constraints.maxHeight) height = Math.min(height, constraints.maxHeight);
329
+
330
+ sendSizeChanged({ width: width, height: height });
331
+ });
332
+
333
+ observer.observe(target);
334
+ return function() { observer.disconnect(); };
335
+ }
336
+
337
+ // ═══════════════════════════════════════════════════════════════════════════
338
+ // WINDOW.PHOTON API
339
+ // ═══════════════════════════════════════════════════════════════════════════
340
+
341
+ window.photon = {
342
+ get toolOutput() { return toolOutput; },
343
+ get toolInput() { return toolInput; },
344
+ get widgetState() { return widgetState; },
345
+ setWidgetState: function(s) {
346
+ widgetState = s;
347
+ postToHost({ type: 'photon:set-state', state: s });
348
+ },
349
+
350
+ onResult: function(cb) { return subscribe(listeners.result, cb); },
351
+ onThemeChange: function(cb) { return subscribe(listeners.themeChange, cb); },
352
+ onProgress: function(cb) { return subscribe(listeners.progress, cb); },
353
+ onStatus: function(cb) { return subscribe(listeners.status, cb); },
354
+ onStream: function(cb) { return subscribe(listeners.stream, cb); },
355
+ onEmit: function(cb) { return subscribe(listeners.emit, cb); },
356
+
357
+ invoke: callTool,
358
+ callTool: callTool,
359
+
360
+ get theme() { return ctx.theme; },
361
+ get locale() { return ctx.locale || 'en-US'; },
362
+ get photon() { return ctx.photon; },
363
+ get method() { return ctx.method; },
364
+ get hostContext() { return hostContext; },
365
+
366
+ sendSizeChanged: sendSizeChanged,
367
+ setupAutoResize: setupAutoResize,
368
+ parseSizeMeta: parseSizeMeta,
369
+
370
+ // Generic event subscription for real-time sync
371
+ // Usage: photon.on('taskMove', function(data) { ... })
372
+ on: function(eventName, cb) {
373
+ if (!eventListeners[eventName]) eventListeners[eventName] = [];
374
+ eventListeners[eventName].push(cb);
375
+ return function() {
376
+ var i = eventListeners[eventName].indexOf(cb);
377
+ if (i >= 0) eventListeners[eventName].splice(i, 1);
378
+ };
379
+ },
380
+
381
+ // Photon-specific event subscription (for injected photon events)
382
+ // Usage: photon.onPhoton('notifications', 'alertCreated', function(data) { ... })
383
+ onPhoton: function(photonName, eventName, cb) {
384
+ if (!photonEventListeners[photonName]) photonEventListeners[photonName] = {};
385
+ if (!photonEventListeners[photonName][eventName]) photonEventListeners[photonName][eventName] = [];
386
+ photonEventListeners[photonName][eventName].push(cb);
387
+ return function() {
388
+ var i = photonEventListeners[photonName][eventName].indexOf(cb);
389
+ if (i >= 0) photonEventListeners[photonName][eventName].splice(i, 1);
390
+ };
391
+ }
392
+ };
393
+
394
+ // Create direct window object: window.{photonName}
395
+ // This provides a clean class-like API that mirrors server methods:
396
+ // Server: this.emit('taskMove', data)
397
+ // Client: kanban.onTaskMove(cb) - subscribe to events
398
+ // Client: kanban.taskMove(args) - call server method
399
+ if (ctx.photon) {
400
+ window[ctx.photon] = new Proxy({}, {
401
+ get: function(target, prop) {
402
+ if (typeof prop !== 'string') return undefined;
403
+
404
+ // onEventName -> subscribe to 'eventName' event
405
+ // e.g., onTaskMove -> subscribe to 'taskMove'
406
+ if (prop.startsWith('on') && prop.length > 2) {
407
+ var eventName = prop.charAt(2).toLowerCase() + prop.slice(3);
408
+ return function(cb) {
409
+ return window.photon.on(eventName, cb);
410
+ };
411
+ }
412
+
413
+ // methodName -> call server tool
414
+ // e.g., taskMove(args) -> photon.callTool(prop, args)
415
+ return function(args) {
416
+ return window.photon.callTool(prop, args);
417
+ };
418
+ }
419
+ });
420
+ }
421
+
422
+ // Create proxies for injected photons (for event subscriptions)
423
+ // e.g., notifications.onAlertCreated(cb) subscribes to 'alertCreated' from 'notifications' photon
424
+ injectedPhotons.forEach(function(injectedName) {
425
+ window[injectedName] = new Proxy({}, {
426
+ get: function(target, prop) {
427
+ if (typeof prop !== 'string') return undefined;
428
+
429
+ // onEventName -> subscribe to photon-specific event
430
+ if (prop.startsWith('on') && prop.length > 2) {
431
+ var eventName = prop.charAt(2).toLowerCase() + prop.slice(3);
432
+ return function(cb) {
433
+ return window.photon.onPhoton(injectedName, eventName, cb);
434
+ };
435
+ }
436
+
437
+ // Method calls on injected photons are not supported from client
438
+ // (injected photon methods are only available server-side)
439
+ return undefined;
440
+ }
441
+ });
442
+ });
443
+
444
+ // ═══════════════════════════════════════════════════════════════════════════
445
+ // WINDOW.OPENAI API (ChatGPT Apps SDK Compatibility)
446
+ // ═══════════════════════════════════════════════════════════════════════════
447
+
448
+ window.openai = {
449
+ get theme() { return ctx.theme; },
450
+ get displayMode() { return 'inline'; },
451
+ get locale() { return ctx.locale || 'en-US'; },
452
+ get maxHeight() { return 800; },
453
+ get toolInput() { return toolInput; },
454
+ get toolOutput() { return toolOutput; },
455
+ get widgetState() { return widgetState; },
456
+ get toolResponseMetadata() { return {}; },
457
+
458
+ setWidgetState: function(state) {
459
+ widgetState = state;
460
+ postToHost({ type: 'photon:set-state', state: state });
461
+ },
462
+ callTool: callTool,
463
+ sendFollowUpMessage: function(opts) {
464
+ postToHost({ type: 'photon:follow-up', message: opts.prompt });
465
+ return Promise.resolve();
466
+ },
467
+ uploadFile: function(file) {
468
+ return Promise.reject(new Error('File upload not supported'));
469
+ },
470
+ getFileDownloadUrl: function(opts) {
471
+ return Promise.reject(new Error('File download not supported'));
472
+ },
473
+ requestDisplayMode: function(mode) {
474
+ return Promise.resolve();
475
+ },
476
+ requestModal: function(opts) {
477
+ return Promise.reject(new Error('Modal not supported'));
478
+ },
479
+ notifyIntrinsicHeight: function(height) {
480
+ sendSizeChanged({ height: height });
481
+ },
482
+ openExternal: function(opts) {
483
+ window.open(opts.href, '_blank', 'noopener,noreferrer');
484
+ },
485
+ setOpenInAppUrl: function(opts) {}
486
+ };
487
+
488
+ // ═══════════════════════════════════════════════════════════════════════════
489
+ // INITIALIZATION
490
+ // ═══════════════════════════════════════════════════════════════════════════
491
+
492
+ // Apply initial theme
493
+ applyThemeTokens();
494
+
495
+ // Notify host that bridge is ready (legacy)
496
+ postToHost({ type: 'photon:ready' });
497
+
498
+ // MCP Apps Extension: Send ui/initialize REQUEST
499
+ var initCallId = generateCallId();
500
+ pendingCalls[initCallId] = {
501
+ resolve: function(result) {
502
+ if (result && result.hostContext) {
503
+ hostContext = result.hostContext;
504
+ if (result.hostContext.theme) ctx.theme = result.hostContext.theme;
505
+ if (result.hostContext.styles && result.hostContext.styles.variables) {
506
+ themeTokens = result.hostContext.styles.variables;
507
+ }
508
+ applyThemeTokens();
509
+ }
510
+ // Send initialized notification
511
+ postToHost({ jsonrpc: '2.0', method: 'ui/notifications/initialized', params: {} });
512
+ },
513
+ reject: function(err) {
514
+ console.debug('MCP Apps init - no response (host may not support full protocol)');
515
+ }
516
+ };
517
+
518
+ postToHost({
519
+ jsonrpc: '2.0',
520
+ id: initCallId,
521
+ method: 'ui/initialize',
522
+ params: {
523
+ appInfo: { name: ctx.photon || 'photon-app', version: '1.0.0' },
524
+ appCapabilities: {},
525
+ protocolVersion: '2026-01-26'
526
+ }
527
+ });
528
+
529
+ // Timeout for init - continue anyway if no response
530
+ setTimeout(function() {
531
+ if (pendingCalls[initCallId]) {
532
+ delete pendingCalls[initCallId];
533
+ }
534
+ }, 5000);
535
+
536
+ })();
537
+ </script>`;
538
+ }
539
+ /**
540
+ * Generate bridge script with legacy compatibility
541
+ *
542
+ * This is a drop-in replacement for generatePlatformBridgeScript from platform-compat.ts
543
+ */
544
+ export function generatePlatformBridgeScript(context) {
545
+ return generateBridgeScript({
546
+ theme: context.theme,
547
+ locale: context.locale,
548
+ photon: context.photon || '',
549
+ method: context.method || '',
550
+ hostName: context.hostName,
551
+ hostVersion: context.hostVersion,
552
+ injectedPhotons: context.injectedPhotons,
553
+ });
554
+ }
555
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/auto-ui/bridge/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAe5D,6DAA6D;AAC7D,gFAAgF;AAEhF;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAA4B;IAC/D,2DAA2D;IAC3D,MAAM,WAAW,GAAG,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAClD,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IACpD,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAE5C,OAAO;;;;;;;;;cASK,WAAW;sBACH,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA6f3B,CAAC;AACX,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,4BAA4B,CAAC,OAS5C;IACC,OAAO,oBAAoB,CAAC;QAC1B,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE;QAC5B,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE;QAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,eAAe,EAAE,OAAO,CAAC,eAAe;KACzC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * OpenAI Apps SDK Compatibility Shim
3
+ *
4
+ * Provides window.openai API that translates to MCP Apps protocol.
5
+ * This allows apps built for ChatGPT to work in Photon hosts.
6
+ */
7
+ import type { PhotonApp } from './photon-app.js';
8
+ import type { OpenAIAPI } from './types.js';
9
+ /**
10
+ * Generate the OpenAI shim code as inline JavaScript
11
+ *
12
+ * This gets injected into the iframe along with the PhotonApp.
13
+ * It creates a window.openai object that delegates to window.photon.
14
+ */
15
+ export declare function generateOpenAIShimCode(): string;
16
+ /**
17
+ * Create OpenAI shim wrapper (for use with PhotonApp in browser)
18
+ */
19
+ export declare function createOpenAIShim(photonApp: PhotonApp): OpenAIAPI;
20
+ //# sourceMappingURL=openai-shim.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-shim.d.ts","sourceRoot":"","sources":["../../../src/auto-ui/bridge/openai-shim.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAkK/C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,CAoEhE"}