@agent-native/core 0.22.18 → 0.22.20
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/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +19 -6
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/embed-auth.d.ts +5 -0
- package/dist/client/embed-auth.d.ts.map +1 -1
- package/dist/client/embed-auth.js +181 -12
- package/dist/client/embed-auth.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/mcp-app-host.d.ts +5 -0
- package/dist/client/mcp-app-host.d.ts.map +1 -1
- package/dist/client/mcp-app-host.js +267 -2
- package/dist/client/mcp-app-host.js.map +1 -1
- package/dist/client/mcp-apps/McpAppRenderer.d.ts +3 -0
- package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/client/mcp-apps/McpAppRenderer.js +86 -9
- package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/client/theme.js +1 -1
- package/dist/client/theme.js.map +1 -1
- package/dist/client/vite-dev-recovery-script.d.ts.map +1 -1
- package/dist/client/vite-dev-recovery-script.js +9 -0
- package/dist/client/vite-dev-recovery-script.js.map +1 -1
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +73 -5
- package/dist/deploy/build.js.map +1 -1
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +73 -8
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +6 -3
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/mcp/embed-app.d.ts +2 -2
- package/dist/mcp/embed-app.d.ts.map +1 -1
- package/dist/mcp/embed-app.js +426 -23
- package/dist/mcp/embed-app.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +37 -10
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/create-server.d.ts.map +1 -1
- package/dist/server/create-server.js +21 -7
- package/dist/server/create-server.js.map +1 -1
- package/dist/server/embed-route.d.ts.map +1 -1
- package/dist/server/embed-route.js +62 -21
- package/dist/server/embed-route.js.map +1 -1
- package/dist/server/open-route.d.ts.map +1 -1
- package/dist/server/open-route.js +7 -2
- package/dist/server/open-route.js.map +1 -1
- package/dist/server/security-headers.d.ts.map +1 -1
- package/dist/server/security-headers.js +9 -1
- package/dist/server/security-headers.js.map +1 -1
- package/dist/server/ssr-handler.d.ts +2 -0
- package/dist/server/ssr-handler.d.ts.map +1 -1
- package/dist/server/ssr-handler.js +66 -11
- package/dist/server/ssr-handler.js.map +1 -1
- package/dist/shared/mcp-embed-headers.d.ts +12 -0
- package/dist/shared/mcp-embed-headers.d.ts.map +1 -0
- package/dist/shared/mcp-embed-headers.js +51 -0
- package/dist/shared/mcp-embed-headers.js.map +1 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +23 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/actions.md +15 -5
- package/docs/content/client.md +6 -3
- package/docs/content/external-agents.md +61 -41
- package/docs/content/mcp-protocol.md +42 -7
- package/package.json +1 -1
package/dist/mcp/embed-app.js
CHANGED
|
@@ -2,8 +2,8 @@ import { MCP_APP_CHAT_BRIDGE_QUERY_PARAM } from "../shared/embed-auth.js";
|
|
|
2
2
|
const MCP_APP_IMPORT = "https://esm.sh/@modelcontextprotocol/ext-apps@1.7.2/app-with-deps";
|
|
3
3
|
export const MCP_APP_REQUEST_ORIGIN_CSP_SOURCE = "$requestOrigin";
|
|
4
4
|
const MCP_APP_WRAPPER_CHROME_HEIGHT = 44;
|
|
5
|
-
export const
|
|
6
|
-
export const
|
|
5
|
+
export const DEFAULT_MCP_APP_SHELL_HEIGHT = 560;
|
|
6
|
+
export const DEFAULT_MCP_APP_VIEWPORT_HEIGHT = DEFAULT_MCP_APP_SHELL_HEIGHT - MCP_APP_WRAPPER_CHROME_HEIGHT;
|
|
7
7
|
function attr(value) {
|
|
8
8
|
return String(value ?? "")
|
|
9
9
|
.replace(/&/g, "&")
|
|
@@ -19,6 +19,10 @@ export function embedApp(options = {}) {
|
|
|
19
19
|
const embedByDefault = options.embedByDefault !== false;
|
|
20
20
|
const height = Math.max(320, Math.min(900, options.height ?? DEFAULT_MCP_APP_SHELL_HEIGHT));
|
|
21
21
|
const viewportHeight = height - MCP_APP_WRAPPER_CHROME_HEIGHT;
|
|
22
|
+
const frameDomains = [
|
|
23
|
+
MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,
|
|
24
|
+
...(options.frameDomains ?? []),
|
|
25
|
+
];
|
|
22
26
|
return {
|
|
23
27
|
title,
|
|
24
28
|
...(options.description ? { description: options.description } : {}),
|
|
@@ -28,19 +32,19 @@ export function embedApp(options = {}) {
|
|
|
28
32
|
<meta charset="utf-8">
|
|
29
33
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
30
34
|
<style>
|
|
31
|
-
:root { color-scheme: light dark; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: Canvas; color: CanvasText; }
|
|
35
|
+
:root { color-scheme: light dark; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: Canvas; color: CanvasText; --agent-native-shell-height: ${height}px; --agent-native-viewport-height: ${viewportHeight}px; }
|
|
32
36
|
* { box-sizing: border-box; }
|
|
33
37
|
body { margin: 0; }
|
|
34
|
-
.shell { display: grid; gap: 8px; min-height:
|
|
38
|
+
.shell { display: grid; gap: 8px; min-height: var(--agent-native-shell-height); padding: 0; }
|
|
35
39
|
.bar { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 36px; padding: 6px 8px; border-bottom: 1px solid color-mix(in srgb, CanvasText 12%, Canvas); }
|
|
36
40
|
.title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; font-weight: 700; color: color-mix(in srgb, CanvasText 72%, Canvas); }
|
|
37
41
|
.actions { display: flex; align-items: center; gap: 6px; }
|
|
38
42
|
button { min-height: 28px; border: 1px solid color-mix(in srgb, CanvasText 14%, Canvas); border-radius: 7px; background: Canvas; color: CanvasText; cursor: pointer; font: inherit; font-size: 12px; font-weight: 700; padding: 0 9px; }
|
|
39
43
|
button:disabled { opacity: .55; cursor: default; }
|
|
40
|
-
.stage { position: relative; min-height:
|
|
41
|
-
iframe { display: block; width: 100%; height:
|
|
42
|
-
.message { display: grid; place-items: center; min-height:
|
|
43
|
-
.fallback { display: grid; align-content: center; justify-items: center; gap: 12px; min-height:
|
|
44
|
+
.stage { position: relative; min-height: var(--agent-native-viewport-height); }
|
|
45
|
+
iframe { display: block; width: 100%; height: var(--agent-native-viewport-height); border: 0; background: Canvas; }
|
|
46
|
+
.message { display: grid; place-items: center; min-height: var(--agent-native-viewport-height); padding: 18px; color: color-mix(in srgb, CanvasText 62%, Canvas); font-size: 13px; line-height: 1.45; text-align: center; }
|
|
47
|
+
.fallback { display: grid; align-content: center; justify-items: center; gap: 12px; min-height: var(--agent-native-viewport-height); padding: 24px; background: Canvas; color: CanvasText; text-align: center; }
|
|
44
48
|
.fallback-title { max-width: 440px; font-size: 14px; font-weight: 700; }
|
|
45
49
|
.fallback-copy { max-width: 520px; color: color-mix(in srgb, CanvasText 64%, Canvas); font-size: 13px; line-height: 1.45; }
|
|
46
50
|
.fallback-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px; }
|
|
@@ -75,7 +79,8 @@ export function embedApp(options = {}) {
|
|
|
75
79
|
const startTool = body.dataset.startTool || "create_embed_session";
|
|
76
80
|
const embedByDefault = body.dataset.embedDefault !== "0";
|
|
77
81
|
const chatBridgeParam = ${JSON.stringify(MCP_APP_CHAT_BRIDGE_QUERY_PARAM)};
|
|
78
|
-
const
|
|
82
|
+
const defaultIntrinsicHeight = ${height};
|
|
83
|
+
const chromeHeight = ${MCP_APP_WRAPPER_CHROME_HEIGHT};
|
|
79
84
|
let app = null;
|
|
80
85
|
let openAiBridge = null;
|
|
81
86
|
let toolInput = {};
|
|
@@ -107,6 +112,40 @@ export function embedApp(options = {}) {
|
|
|
107
112
|
: {};
|
|
108
113
|
}
|
|
109
114
|
|
|
115
|
+
function finiteNumber(value) {
|
|
116
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
117
|
+
? value
|
|
118
|
+
: null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function contextMaxHeight(context) {
|
|
122
|
+
if (!context || typeof context !== "object") return null;
|
|
123
|
+
return finiteNumber(context.maxHeight) ||
|
|
124
|
+
finiteNumber(context.containerDimensions && context.containerDimensions.maxHeight);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function visibleIntrinsicHeight() {
|
|
128
|
+
const context = hostState().context || {};
|
|
129
|
+
const hostMaxHeight = contextMaxHeight(context);
|
|
130
|
+
if (hostMaxHeight) return Math.floor(hostMaxHeight);
|
|
131
|
+
const viewportHeight = finiteNumber(window.visualViewport && window.visualViewport.height) ||
|
|
132
|
+
finiteNumber(window.innerHeight);
|
|
133
|
+
return Math.floor(viewportHeight || defaultIntrinsicHeight);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function applyIntrinsicHeight(nextHeight) {
|
|
137
|
+
const boundedHeight = Math.min(
|
|
138
|
+
defaultIntrinsicHeight,
|
|
139
|
+
Math.floor(nextHeight || defaultIntrinsicHeight)
|
|
140
|
+
);
|
|
141
|
+
const height = Math.max(320, boundedHeight);
|
|
142
|
+
const viewportHeight = Math.max(0, height - chromeHeight);
|
|
143
|
+
document.documentElement.style.setProperty("--agent-native-shell-height", height + "px");
|
|
144
|
+
document.documentElement.style.setProperty("--agent-native-viewport-height", viewportHeight + "px");
|
|
145
|
+
if (appFrame) appFrame.style.height = viewportHeight + "px";
|
|
146
|
+
return height;
|
|
147
|
+
}
|
|
148
|
+
|
|
110
149
|
function parseToolResult(params) {
|
|
111
150
|
if (!params) return {};
|
|
112
151
|
if (params.result && typeof params.result === "object") {
|
|
@@ -124,8 +163,9 @@ export function embedApp(options = {}) {
|
|
|
124
163
|
}
|
|
125
164
|
|
|
126
165
|
function openLinkFrom(params, data) {
|
|
127
|
-
const
|
|
128
|
-
|
|
166
|
+
const openLink = params && params._meta && params._meta["agent-native/openLink"];
|
|
167
|
+
const metaUrl = openLink && typeof openLink === "object" && typeof openLink.webUrl === "string"
|
|
168
|
+
? openLink.webUrl
|
|
129
169
|
: "";
|
|
130
170
|
return metaUrl || data.url || data.deepLink || data.openUrl || "";
|
|
131
171
|
}
|
|
@@ -187,6 +227,270 @@ export function embedApp(options = {}) {
|
|
|
187
227
|
}
|
|
188
228
|
}
|
|
189
229
|
|
|
230
|
+
function isEmbedStartUrl(value) {
|
|
231
|
+
if (typeof value !== "string" || !value) return false;
|
|
232
|
+
try {
|
|
233
|
+
const url = new URL(value, window.location.href);
|
|
234
|
+
return /\\/_agent-native\\/embed\\/start$/.test(url.pathname);
|
|
235
|
+
} catch {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function localPathFromUrl(url, includeToken) {
|
|
241
|
+
const next = new URL(url.href);
|
|
242
|
+
if (!includeToken) next.searchParams.delete("__an_embed_token");
|
|
243
|
+
return next.pathname + next.search + next.hash;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function rewriteRootRelativeHtmlUrls(html, appOrigin) {
|
|
247
|
+
return String(html).replace(
|
|
248
|
+
/\\b(src|href|poster|action)\\s*=\\s*(["'])\\/(?!\\/)/gi,
|
|
249
|
+
(_match, name, quote) => String(name) + "=" + quote + appOrigin + "/"
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function removeHtmlCspMeta(html) {
|
|
254
|
+
return String(html).replace(
|
|
255
|
+
/<meta\\s+[^>]*http-equiv\\s*=\\s*(["'])?content-security-policy\\1?[^>]*>/gi,
|
|
256
|
+
""
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function embedConfigForAppUrl(appUrl) {
|
|
261
|
+
const sanitizedTarget = localPathFromUrl(appUrl, false);
|
|
262
|
+
return {
|
|
263
|
+
origin: appUrl.origin,
|
|
264
|
+
href: appUrl.href,
|
|
265
|
+
baseHref: appUrl.origin + appUrl.pathname,
|
|
266
|
+
target: sanitizedTarget,
|
|
267
|
+
token: appUrl.searchParams.get("__an_embed_token") || "",
|
|
268
|
+
chatBridgeActive: appUrl.searchParams.get(chatBridgeParam) === "1",
|
|
269
|
+
chatBridgeParam,
|
|
270
|
+
embedTokenParam: "__an_embed_token",
|
|
271
|
+
embedTargetHeader: "x-agent-native-embed-target"
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function installExternalEmbedRuntime(config) {
|
|
276
|
+
window.__AGENT_NATIVE_EXTERNAL_EMBED = config;
|
|
277
|
+
try {
|
|
278
|
+
if (config.target) {
|
|
279
|
+
window.history.replaceState(window.history.state, "", config.target);
|
|
280
|
+
}
|
|
281
|
+
} catch (_err) {}
|
|
282
|
+
try {
|
|
283
|
+
if (config.token) {
|
|
284
|
+
sessionStorage.setItem("agent-native:embed-auth-token", config.token);
|
|
285
|
+
}
|
|
286
|
+
if (config.chatBridgeActive && config.token) {
|
|
287
|
+
sessionStorage.setItem("agent-native:mcp-chat-bridge", config.token);
|
|
288
|
+
}
|
|
289
|
+
} catch (_err) {}
|
|
290
|
+
if (window.__agentNativeExternalEmbedRuntimeInstalled) return;
|
|
291
|
+
window.__agentNativeExternalEmbedRuntimeInstalled = true;
|
|
292
|
+
function appOrigin() {
|
|
293
|
+
try {
|
|
294
|
+
return new URL(config.origin).origin;
|
|
295
|
+
} catch (_err) {
|
|
296
|
+
return "";
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function targetPath() {
|
|
300
|
+
return config.target || location.pathname + location.search;
|
|
301
|
+
}
|
|
302
|
+
function rewrittenUrl(value, appendToken) {
|
|
303
|
+
const origin = appOrigin();
|
|
304
|
+
if (!origin) return null;
|
|
305
|
+
let url;
|
|
306
|
+
try {
|
|
307
|
+
url = new URL(value, location.href);
|
|
308
|
+
} catch (_err) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
if (url.origin !== location.origin && url.origin !== origin) return null;
|
|
312
|
+
if (url.origin !== origin) {
|
|
313
|
+
const app = new URL(origin);
|
|
314
|
+
url.protocol = app.protocol;
|
|
315
|
+
url.host = app.host;
|
|
316
|
+
}
|
|
317
|
+
if (appendToken && config.token && url.pathname === "/_agent-native/events") {
|
|
318
|
+
url.searchParams.set(config.embedTokenParam, config.token);
|
|
319
|
+
}
|
|
320
|
+
return url.toString();
|
|
321
|
+
}
|
|
322
|
+
function authHeaders(input, init) {
|
|
323
|
+
const headers = new Headers(
|
|
324
|
+
init && init.headers ? init.headers : input instanceof Request ? input.headers : undefined
|
|
325
|
+
);
|
|
326
|
+
if (config.token && !headers.has("Authorization")) {
|
|
327
|
+
headers.set("Authorization", "Bearer " + config.token);
|
|
328
|
+
}
|
|
329
|
+
if (!headers.has(config.embedTargetHeader)) {
|
|
330
|
+
headers.set(config.embedTargetHeader, targetPath());
|
|
331
|
+
}
|
|
332
|
+
return headers;
|
|
333
|
+
}
|
|
334
|
+
if (typeof fetch === "function") {
|
|
335
|
+
const originalFetch = fetch.bind(window);
|
|
336
|
+
window.fetch = function(input, init) {
|
|
337
|
+
const raw = input instanceof Request ? input.url : String(input);
|
|
338
|
+
const url = rewrittenUrl(raw, false);
|
|
339
|
+
if (!url) return originalFetch(input, init);
|
|
340
|
+
const nextInit = Object.assign({}, init || {}, {
|
|
341
|
+
headers: authHeaders(input, init),
|
|
342
|
+
credentials: "omit"
|
|
343
|
+
});
|
|
344
|
+
if (input instanceof Request) {
|
|
345
|
+
return originalFetch(new Request(url, input), nextInit);
|
|
346
|
+
}
|
|
347
|
+
return originalFetch(url, nextInit);
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
if (typeof XMLHttpRequest !== "undefined") {
|
|
351
|
+
const originalOpen = XMLHttpRequest.prototype.open;
|
|
352
|
+
const originalSend = XMLHttpRequest.prototype.send;
|
|
353
|
+
XMLHttpRequest.prototype.open = function(method, url) {
|
|
354
|
+
const rewritten = rewrittenUrl(url, false);
|
|
355
|
+
this.__agentNativeExternalEmbed = !!rewritten;
|
|
356
|
+
return originalOpen.call(
|
|
357
|
+
this,
|
|
358
|
+
method,
|
|
359
|
+
rewritten || url,
|
|
360
|
+
arguments.length > 2 ? arguments[2] : true,
|
|
361
|
+
arguments[3],
|
|
362
|
+
arguments[4]
|
|
363
|
+
);
|
|
364
|
+
};
|
|
365
|
+
XMLHttpRequest.prototype.send = function(body) {
|
|
366
|
+
if (this.__agentNativeExternalEmbed) {
|
|
367
|
+
try {
|
|
368
|
+
if (config.token) this.setRequestHeader("Authorization", "Bearer " + config.token);
|
|
369
|
+
this.setRequestHeader(config.embedTargetHeader, targetPath());
|
|
370
|
+
} catch (_err) {}
|
|
371
|
+
}
|
|
372
|
+
return originalSend.call(this, body);
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
if (typeof EventSource !== "undefined") {
|
|
376
|
+
const OriginalEventSource = EventSource;
|
|
377
|
+
window.EventSource = function(url, options) {
|
|
378
|
+
return new OriginalEventSource(rewrittenUrl(url, true) || url, options);
|
|
379
|
+
};
|
|
380
|
+
window.EventSource.prototype = OriginalEventSource.prototype;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function copyDocumentElementAttributes(source) {
|
|
385
|
+
const target = document.documentElement;
|
|
386
|
+
for (const attr of Array.from(target.attributes)) {
|
|
387
|
+
target.removeAttribute(attr.name);
|
|
388
|
+
}
|
|
389
|
+
for (const attr of Array.from(source.attributes)) {
|
|
390
|
+
target.setAttribute(attr.name, attr.value);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function importChildren(source, target) {
|
|
395
|
+
target.replaceChildren(
|
|
396
|
+
...Array.from(source.childNodes).map((node) => document.importNode(node, true))
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function isModuleScript(script) {
|
|
401
|
+
return (script.getAttribute("type") || "").trim().toLowerCase() === "module";
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function isRunnableClassicScript(script) {
|
|
405
|
+
const type = (script.getAttribute("type") || "").trim().toLowerCase();
|
|
406
|
+
return !type || type === "text/javascript" || type === "application/javascript";
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function runClassicScript(script) {
|
|
410
|
+
const next = document.createElement("script");
|
|
411
|
+
for (const attr of Array.from(script.attributes)) {
|
|
412
|
+
if (attr.name === "type") continue;
|
|
413
|
+
next.setAttribute(attr.name, attr.value);
|
|
414
|
+
}
|
|
415
|
+
if (script.src) {
|
|
416
|
+
next.src = script.src;
|
|
417
|
+
} else {
|
|
418
|
+
next.textContent = script.textContent || "";
|
|
419
|
+
}
|
|
420
|
+
document.body.appendChild(next);
|
|
421
|
+
next.remove();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function rootRelativeSpecifiersToAbsolute(code, appOrigin) {
|
|
425
|
+
return String(code).replace(/(["'])\\/(?!\\/)/g, "$1" + appOrigin + "/");
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function moduleCodeToClassicAsync(code, appOrigin) {
|
|
429
|
+
return rootRelativeSpecifiersToAbsolute(code, appOrigin)
|
|
430
|
+
.replace(
|
|
431
|
+
/\\bimport\\s+\\*\\s+as\\s+([A-Za-z_$][\\w$]*)\\s+from\\s+(["'][^"']+["'])\\s*;?/g,
|
|
432
|
+
"const $1 = await import($2);"
|
|
433
|
+
)
|
|
434
|
+
.replace(/\\bimport\\s+(["'][^"']+["'])\\s*;?/g, "await import($1);")
|
|
435
|
+
.replace(/\\bimport\\((["'][^"']+["'])\\)\\s*;?/g, "await import($1);");
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function runModuleScriptAsClassic(script, appOrigin) {
|
|
439
|
+
const code = moduleCodeToClassicAsync(script.textContent || "", appOrigin);
|
|
440
|
+
const runner = document.createElement("script");
|
|
441
|
+
runner.textContent =
|
|
442
|
+
"(async()=>{" +
|
|
443
|
+
code +
|
|
444
|
+
"})().catch((err)=>{console.error('[agent-native] transplanted app module failed',err);document.body.setAttribute('data-agent-native-hydration-error',String(err&&err.message||err));});";
|
|
445
|
+
document.body.appendChild(runner);
|
|
446
|
+
runner.remove();
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function mountTransplantedHtml(html, appUrl) {
|
|
450
|
+
const config = embedConfigForAppUrl(appUrl);
|
|
451
|
+
installExternalEmbedRuntime(config);
|
|
452
|
+
const parsed = new DOMParser().parseFromString(
|
|
453
|
+
rewriteRootRelativeHtmlUrls(removeHtmlCspMeta(html), appUrl.origin),
|
|
454
|
+
"text/html"
|
|
455
|
+
);
|
|
456
|
+
const scripts = Array.from(parsed.querySelectorAll("script"));
|
|
457
|
+
copyDocumentElementAttributes(parsed.documentElement);
|
|
458
|
+
importChildren(parsed.head, document.head);
|
|
459
|
+
const base = document.createElement("base");
|
|
460
|
+
base.href = config.baseHref;
|
|
461
|
+
document.head.prepend(base);
|
|
462
|
+
importChildren(parsed.body, document.body);
|
|
463
|
+
for (const script of scripts) {
|
|
464
|
+
if (isRunnableClassicScript(script)) runClassicScript(script);
|
|
465
|
+
}
|
|
466
|
+
for (const script of scripts) {
|
|
467
|
+
if (isModuleScript(script)) runModuleScriptAsClassic(script, appUrl.origin);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async function transplantAppDocument(src) {
|
|
472
|
+
clearFrameReadyTimer();
|
|
473
|
+
clearFrameLoadTimer();
|
|
474
|
+
appFrame = null;
|
|
475
|
+
lastFrameSrc = src;
|
|
476
|
+
setMessage("Loading app");
|
|
477
|
+
const response = await fetch(src, {
|
|
478
|
+
credentials: "omit",
|
|
479
|
+
redirect: "follow",
|
|
480
|
+
headers: { Accept: "text/html" }
|
|
481
|
+
});
|
|
482
|
+
if (!response.ok) {
|
|
483
|
+
throw new Error("Embedded app returned HTTP " + response.status + ".");
|
|
484
|
+
}
|
|
485
|
+
const html = await response.text();
|
|
486
|
+
const appUrl = new URL(response.url || src);
|
|
487
|
+
try {
|
|
488
|
+
window.history.replaceState(window.history.state, "", localPathFromUrl(appUrl, false));
|
|
489
|
+
} catch {}
|
|
490
|
+
mountTransplantedHtml(html, appUrl);
|
|
491
|
+
notifyHostHeightRepeatedly();
|
|
492
|
+
}
|
|
493
|
+
|
|
190
494
|
function wantsEmbed() {
|
|
191
495
|
if (toolInput.embed === false || toolInput.embed === "false") return false;
|
|
192
496
|
if (embedByDefault) return true;
|
|
@@ -285,11 +589,10 @@ export function embedApp(options = {}) {
|
|
|
285
589
|
}
|
|
286
590
|
|
|
287
591
|
async function openFallbackExternal() {
|
|
288
|
-
let url = openUrl;
|
|
592
|
+
let url = withChatBridgeParam(openUrl);
|
|
289
593
|
try {
|
|
290
|
-
const embedUrl = withChatBridgeParam(openUrl);
|
|
291
594
|
const result = await callEmbedSessionTool({
|
|
292
|
-
url
|
|
595
|
+
url,
|
|
293
596
|
chrome: typeof toolInput.chrome === "string" ? toolInput.chrome : "full"
|
|
294
597
|
});
|
|
295
598
|
const data = parseToolResult(result);
|
|
@@ -325,6 +628,53 @@ export function embedApp(options = {}) {
|
|
|
325
628
|
}, 30000);
|
|
326
629
|
}
|
|
327
630
|
|
|
631
|
+
function shouldSelfNavigateToApp() {
|
|
632
|
+
const mode = typeof toolInput.embedMode === "string"
|
|
633
|
+
? toolInput.embedMode
|
|
634
|
+
: typeof toolInput.renderMode === "string"
|
|
635
|
+
? toolInput.renderMode
|
|
636
|
+
: "";
|
|
637
|
+
if (mode === "iframe" || mode === "nested") return false;
|
|
638
|
+
if (toolInput.nested === true || toolInput.frame === "iframe") return false;
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function isClaudeMcpContentHost() {
|
|
643
|
+
try {
|
|
644
|
+
return /(^|\\.)claudemcpcontent\\.com$/i.test(window.location.hostname || "");
|
|
645
|
+
} catch {
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function isChatGptSandboxHost() {
|
|
651
|
+
try {
|
|
652
|
+
const host = window.location.hostname || "";
|
|
653
|
+
const appParam = new URL(window.location.href).searchParams.get("app");
|
|
654
|
+
return /(^|\\.)oaiusercontent\\.com$/i.test(host) || appParam === "chatgpt";
|
|
655
|
+
} catch {
|
|
656
|
+
return false;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function shouldRenderControlledAppFrame() {
|
|
661
|
+
return !!openAiBridge || isChatGptSandboxHost();
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function navigateToAppFrame(src) {
|
|
665
|
+
clearFrameReadyTimer();
|
|
666
|
+
clearFrameLoadTimer();
|
|
667
|
+
appFrame = null;
|
|
668
|
+
lastFrameSrc = src;
|
|
669
|
+
setMessage("Opening app");
|
|
670
|
+
try {
|
|
671
|
+
window.location.replace(src);
|
|
672
|
+
} catch (err) {
|
|
673
|
+
console.warn("[agent-native] MCP app self-navigation failed", err);
|
|
674
|
+
renderFrameFallback();
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
328
678
|
async function updateHostModelContext(data) {
|
|
329
679
|
const params = {};
|
|
330
680
|
if (Array.isArray(data && data.content)) params.content = data.content;
|
|
@@ -357,11 +707,19 @@ export function embedApp(options = {}) {
|
|
|
357
707
|
}
|
|
358
708
|
|
|
359
709
|
function notifyHostHeight() {
|
|
710
|
+
const height = applyIntrinsicHeight(visibleIntrinsicHeight());
|
|
360
711
|
if (!openAiBridge || typeof openAiBridge.notifyIntrinsicHeight !== "function") {
|
|
712
|
+
if (app && typeof app.sendSizeChanged === "function") {
|
|
713
|
+
try {
|
|
714
|
+
app.sendSizeChanged({ height });
|
|
715
|
+
} catch (err) {
|
|
716
|
+
console.warn("[agent-native] MCP host rejected size update", err);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
361
719
|
return;
|
|
362
720
|
}
|
|
363
721
|
try {
|
|
364
|
-
openAiBridge.notifyIntrinsicHeight({ height
|
|
722
|
+
openAiBridge.notifyIntrinsicHeight({ height });
|
|
365
723
|
} catch (err) {
|
|
366
724
|
console.warn("[agent-native] ChatGPT rejected intrinsic height update", err);
|
|
367
725
|
}
|
|
@@ -457,6 +815,22 @@ export function embedApp(options = {}) {
|
|
|
457
815
|
}
|
|
458
816
|
});
|
|
459
817
|
|
|
818
|
+
function notifyHostHeightSoon() {
|
|
819
|
+
requestAnimationFrame(() => notifyHostHeight());
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function notifyHostHeightRepeatedly() {
|
|
823
|
+
notifyHostHeight();
|
|
824
|
+
[0, 250, 1000, 2500].forEach((delay) => {
|
|
825
|
+
setTimeout(() => notifyHostHeight(), delay);
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
window.addEventListener("resize", notifyHostHeightSoon, { passive: true });
|
|
830
|
+
if (window.visualViewport) {
|
|
831
|
+
window.visualViewport.addEventListener("resize", notifyHostHeightSoon, { passive: true });
|
|
832
|
+
}
|
|
833
|
+
|
|
460
834
|
async function launchEmbed() {
|
|
461
835
|
if (!openUrl) {
|
|
462
836
|
setMessage("Open link was not available.");
|
|
@@ -470,18 +844,43 @@ export function embedApp(options = {}) {
|
|
|
470
844
|
startedFor = openUrl;
|
|
471
845
|
setMessage("Loading app");
|
|
472
846
|
try {
|
|
847
|
+
const selfNavigate = shouldSelfNavigateToApp();
|
|
473
848
|
const embedUrl = withChatBridgeParam(openUrl);
|
|
849
|
+
if (selfNavigate && isEmbedStartUrl(embedUrl)) {
|
|
850
|
+
if (isClaudeMcpContentHost()) {
|
|
851
|
+
await transplantAppDocument(embedUrl);
|
|
852
|
+
} else if (shouldRenderControlledAppFrame()) {
|
|
853
|
+
renderFrame(embedUrl);
|
|
854
|
+
} else {
|
|
855
|
+
navigateToAppFrame(embedUrl);
|
|
856
|
+
}
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
if (!selfNavigate && isEmbedStartUrl(embedUrl)) {
|
|
860
|
+
renderFrame(embedUrl);
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
474
863
|
const result = await callEmbedSessionTool({
|
|
475
864
|
url: embedUrl,
|
|
476
865
|
chrome: typeof toolInput.chrome === "string" ? toolInput.chrome : "full"
|
|
477
866
|
});
|
|
478
867
|
const data = parseToolResult(result);
|
|
479
|
-
if (!data.startUrl) {
|
|
868
|
+
if (typeof data.startUrl !== "string" || !data.startUrl) {
|
|
480
869
|
startedFor = "";
|
|
481
870
|
setMessage(data.error || "This app can be opened, but not embedded from this MCP server.");
|
|
482
871
|
return;
|
|
483
872
|
}
|
|
484
|
-
|
|
873
|
+
if (selfNavigate) {
|
|
874
|
+
if (isClaudeMcpContentHost()) {
|
|
875
|
+
await transplantAppDocument(data.startUrl);
|
|
876
|
+
} else if (shouldRenderControlledAppFrame()) {
|
|
877
|
+
renderFrame(data.startUrl);
|
|
878
|
+
} else {
|
|
879
|
+
navigateToAppFrame(data.startUrl);
|
|
880
|
+
}
|
|
881
|
+
} else {
|
|
882
|
+
renderFrame(data.startUrl);
|
|
883
|
+
}
|
|
485
884
|
} catch (err) {
|
|
486
885
|
startedFor = "";
|
|
487
886
|
setMessage(err && err.message ? err.message : "Could not launch embedded app.");
|
|
@@ -588,7 +987,11 @@ export function embedApp(options = {}) {
|
|
|
588
987
|
|
|
589
988
|
async function startMcpAppsBridge() {
|
|
590
989
|
const { App } = await import("${MCP_APP_IMPORT}");
|
|
591
|
-
app = new App(
|
|
990
|
+
app = new App(
|
|
991
|
+
{ name: "Agent Native Embed", version: "1.0.0" },
|
|
992
|
+
{},
|
|
993
|
+
{ autoResize: false }
|
|
994
|
+
);
|
|
592
995
|
app.ontoolinput = (params) => {
|
|
593
996
|
toolInput = params.arguments || {};
|
|
594
997
|
};
|
|
@@ -601,10 +1004,12 @@ export function embedApp(options = {}) {
|
|
|
601
1004
|
};
|
|
602
1005
|
app.onhostcontextchanged = () => {
|
|
603
1006
|
updateDisplayButton();
|
|
1007
|
+
notifyHostHeight();
|
|
604
1008
|
sendHostContext();
|
|
605
1009
|
};
|
|
606
1010
|
await app.connect();
|
|
607
1011
|
updateDisplayButton();
|
|
1012
|
+
notifyHostHeight();
|
|
608
1013
|
sendHostContext();
|
|
609
1014
|
}
|
|
610
1015
|
|
|
@@ -616,16 +1021,14 @@ export function embedApp(options = {}) {
|
|
|
616
1021
|
</body>
|
|
617
1022
|
</html>`,
|
|
618
1023
|
csp: {
|
|
619
|
-
connectDomains: ["https://esm.sh"],
|
|
1024
|
+
connectDomains: ["https://esm.sh", MCP_APP_REQUEST_ORIGIN_CSP_SOURCE],
|
|
620
1025
|
resourceDomains: [
|
|
621
1026
|
"https://esm.sh",
|
|
622
1027
|
MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,
|
|
623
1028
|
...(options.frameDomains ?? []),
|
|
624
1029
|
],
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
...(options.frameDomains ?? []),
|
|
628
|
-
],
|
|
1030
|
+
baseUriDomains: [MCP_APP_REQUEST_ORIGIN_CSP_SOURCE],
|
|
1031
|
+
frameDomains,
|
|
629
1032
|
},
|
|
630
1033
|
prefersBorder: false,
|
|
631
1034
|
};
|