@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.
- package/README.md +361 -339
- package/dist/auto-ui/beam.d.ts +5 -0
- package/dist/auto-ui/beam.d.ts.map +1 -1
- package/dist/auto-ui/beam.js +727 -51
- package/dist/auto-ui/beam.js.map +1 -1
- package/dist/auto-ui/bridge/index.d.ts +37 -0
- package/dist/auto-ui/bridge/index.d.ts.map +1 -0
- package/dist/auto-ui/bridge/index.js +555 -0
- package/dist/auto-ui/bridge/index.js.map +1 -0
- package/dist/auto-ui/bridge/openai-shim.d.ts +20 -0
- package/dist/auto-ui/bridge/openai-shim.d.ts.map +1 -0
- package/dist/auto-ui/bridge/openai-shim.js +231 -0
- package/dist/auto-ui/bridge/openai-shim.js.map +1 -0
- package/dist/auto-ui/bridge/photon-app.d.ts +162 -0
- package/dist/auto-ui/bridge/photon-app.d.ts.map +1 -0
- package/dist/auto-ui/bridge/photon-app.js +460 -0
- package/dist/auto-ui/bridge/photon-app.js.map +1 -0
- package/dist/auto-ui/bridge/types.d.ts +128 -0
- package/dist/auto-ui/bridge/types.d.ts.map +1 -0
- package/dist/auto-ui/bridge/types.js +7 -0
- package/dist/auto-ui/bridge/types.js.map +1 -0
- package/dist/auto-ui/design-system/tokens.d.ts +1 -1
- package/dist/auto-ui/design-system/tokens.d.ts.map +1 -1
- package/dist/auto-ui/design-system/tokens.js +1 -1
- package/dist/auto-ui/design-system/tokens.js.map +1 -1
- package/dist/auto-ui/index.d.ts +3 -1
- package/dist/auto-ui/index.d.ts.map +1 -1
- package/dist/auto-ui/index.js +5 -2
- package/dist/auto-ui/index.js.map +1 -1
- package/dist/auto-ui/platform-compat.d.ts.map +1 -1
- package/dist/auto-ui/platform-compat.js +60 -6
- package/dist/auto-ui/platform-compat.js.map +1 -1
- package/dist/auto-ui/streamable-http-transport.d.ts +25 -1
- package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -1
- package/dist/auto-ui/streamable-http-transport.js +581 -20
- package/dist/auto-ui/streamable-http-transport.js.map +1 -1
- package/dist/auto-ui/types.d.ts +74 -0
- package/dist/auto-ui/types.d.ts.map +1 -1
- package/dist/auto-ui/types.js +21 -0
- package/dist/auto-ui/types.js.map +1 -1
- package/dist/beam.bundle.js +51422 -1791
- package/dist/beam.bundle.js.map +4 -4
- package/dist/cli.js +12 -2
- package/dist/cli.js.map +1 -1
- package/dist/daemon/client.d.ts +5 -3
- package/dist/daemon/client.d.ts.map +1 -1
- package/dist/daemon/client.js +30 -4
- package/dist/daemon/client.js.map +1 -1
- package/dist/daemon/manager.d.ts +5 -0
- package/dist/daemon/manager.d.ts.map +1 -1
- package/dist/daemon/manager.js +20 -0
- package/dist/daemon/manager.js.map +1 -1
- package/dist/loader.d.ts +23 -0
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +77 -12
- package/dist/loader.js.map +1 -1
- package/dist/photon-cli-runner.d.ts.map +1 -1
- package/dist/photon-cli-runner.js +2 -0
- package/dist/photon-cli-runner.js.map +1 -1
- package/dist/photon-doc-extractor.d.ts +1 -0
- package/dist/photon-doc-extractor.d.ts.map +1 -1
- package/dist/photon-doc-extractor.js +25 -6
- package/dist/photon-doc-extractor.js.map +1 -1
- package/dist/server.d.ts +12 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +386 -13
- package/dist/server.js.map +1 -1
- package/dist/template-manager.js +2 -2
- package/dist/version.d.ts +8 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +16 -0
- package/dist/version.js.map +1 -1
- 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"}
|