@agent-native/core 0.84.18 → 0.84.21
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +21 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/caller-auth.ts +37 -8
- package/corpus/core/src/a2a/client.ts +178 -67
- package/corpus/core/src/agent/production-agent.ts +19 -3
- package/corpus/core/src/agent/run-store.ts +180 -13
- package/corpus/core/src/agent/types.ts +1 -1
- package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
- package/corpus/core/src/cli/recap.ts +43 -34
- package/corpus/core/src/client/AssistantChat.tsx +49 -0
- package/corpus/core/src/client/PoweredByBadge.tsx +70 -17
- package/corpus/core/src/client/active-run-state.ts +30 -0
- package/corpus/core/src/client/agent-chat-adapter.ts +5 -1
- package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +41 -3
- package/corpus/core/src/client/extensions/ExtensionViewer.tsx +133 -0
- package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +2 -2
- package/corpus/core/src/client/i18n.tsx +10 -5
- package/corpus/core/src/client/sse-event-processor.ts +147 -5
- package/corpus/core/src/extensions/html-shell.ts +17 -3
- package/corpus/core/src/extensions/theme.ts +39 -0
- package/corpus/core/src/file-upload/builder.ts +35 -14
- package/corpus/core/src/file-upload/types.ts +3 -0
- package/corpus/core/src/integrations/a2a-continuation-processor.ts +49 -24
- package/corpus/core/src/mcp/builtin-tools.ts +3 -0
- package/corpus/core/src/mcp/org-directory.ts +66 -20
- package/corpus/core/src/scripts/call-agent.ts +7 -2
- package/corpus/templates/analytics/actions/provider-api-request.ts +8 -1
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +2 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +91 -33
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +161 -12
- package/corpus/templates/analytics/changelog/2026-07-01-session-replays-load-large-production-recordings-from-scoped.md +6 -0
- package/corpus/templates/calendar/app/pages/BookingPage.tsx +14 -8
- package/corpus/templates/calendar/changelog/2026-07-01-meeting-invite-pages-have-cleaner-branding-a-black-dark-mo.md +6 -0
- package/corpus/templates/clips/actions/create-recording.ts +7 -2
- package/corpus/templates/clips/actions/finalize-recording.ts +11 -0
- package/corpus/templates/clips/actions/get-recording-player-data.ts +4 -2
- package/corpus/templates/clips/actions/import-loom-recording.ts +1 -0
- package/corpus/templates/clips/actions/lib/loom-video.ts +16 -0
- package/corpus/templates/clips/app/components/player/scrubber.tsx +2 -2
- package/corpus/templates/clips/app/components/recorder/countdown-overlay.tsx +4 -3
- package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +39 -12
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +65 -1
- package/corpus/templates/clips/app/components/recorder/recording-toolbar.tsx +1 -22
- package/corpus/templates/clips/app/routes/record.tsx +48 -8
- package/corpus/templates/clips/changelog/2026-07-01-chrome-recording-uploads-now-serialize-chunks-to-avoid-resum.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-clip-uploads-and-loom-imports-finish-sooner-by-skipping-the-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-clip-uploads-and-playback-recover-more-reliably-from-storage.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-desktop-recording-avoids-unsupported-browser-tab-capture.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-embedded-player-progress-is-easier-to-see-on-dark-videos.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-loom-imports-show-clear-guidance-when-loom-does-not-provide-a-downloadable-video.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-preserve-in-flight-streaming-uploads-when-the-streaming-kill.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-recording-controls-are-cleaner-and-the-countdown-uses-shadow.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-recordings-now-recover-when-final-upload-status-gets-lost.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +7 -224
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +94 -29
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +87 -5
- package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +7 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +14 -1
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +4 -1
- package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +96 -1
- package/corpus/templates/clips/shared/finalize-recovery.ts +242 -0
- package/corpus/templates/design/changelog/2026-07-01-design-recovers-sooner-when-an-agent-gets-stuck-preparing-a-.md +6 -0
- package/corpus/templates/plan/app/global.css +1 -1
- package/dist/a2a/caller-auth.d.ts +1 -0
- package/dist/a2a/caller-auth.d.ts.map +1 -1
- package/dist/a2a/caller-auth.js +34 -8
- package/dist/a2a/caller-auth.js.map +1 -1
- package/dist/a2a/client.d.ts +4 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +148 -58
- package/dist/a2a/client.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +19 -4
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-store.d.ts +19 -6
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +163 -13
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/types.d.ts +1 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +1 -1
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/cli/recap.d.ts +3 -3
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +41 -34
- package/dist/cli/recap.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +2 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +36 -2
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/PoweredByBadge.d.ts +9 -3
- package/dist/client/PoweredByBadge.d.ts.map +1 -1
- package/dist/client/PoweredByBadge.js +47 -15
- package/dist/client/PoweredByBadge.js.map +1 -1
- package/dist/client/active-run-state.d.ts +3 -0
- package/dist/client/active-run-state.d.ts.map +1 -1
- package/dist/client/active-run-state.js +24 -0
- package/dist/client/active-run-state.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +5 -1
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +32 -3
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +59 -2
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js +2 -2
- package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
- package/dist/client/i18n.d.ts +1 -1
- package/dist/client/i18n.d.ts.map +1 -1
- package/dist/client/i18n.js +6 -2
- package/dist/client/i18n.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +2 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +121 -5
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/extensions/html-shell.d.ts.map +1 -1
- package/dist/extensions/html-shell.js +17 -3
- package/dist/extensions/html-shell.js.map +1 -1
- package/dist/extensions/theme.d.ts +9 -0
- package/dist/extensions/theme.d.ts.map +1 -1
- package/dist/extensions/theme.js +38 -0
- package/dist/extensions/theme.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/file-upload/builder.d.ts.map +1 -1
- package/dist/file-upload/builder.js +22 -8
- package/dist/file-upload/builder.js.map +1 -1
- package/dist/file-upload/types.d.ts +5 -1
- package/dist/file-upload/types.d.ts.map +1 -1
- package/dist/file-upload/types.js.map +1 -1
- package/dist/integrations/a2a-continuation-processor.js +43 -18
- package/dist/integrations/a2a-continuation-processor.js.map +1 -1
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +3 -0
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/mcp/org-directory.d.ts.map +1 -1
- package/dist/mcp/org-directory.js +58 -20
- package/dist/mcp/org-directory.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +6 -6
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +2 -2
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html-shell.js","sourceRoot":"","sources":["../../src/extensions/html-shell.ts"],"names":[],"mappings":"AAAA,MAAM,yBAAyB,GAC7B,mXAAmX,CAAC;AAEtX,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,QAAQ;IACR,0BAA0B;IAC1B,4BAA4B;IAC5B,oBAAoB;IACpB,oBAAoB;IACpB,gCAAgC;IAChC,0CAA0C;CAC3C,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAEZ,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,yBAAyB,oBAAoB,yBAAyB,GAAG,CAAC;AAEjH,MAAM,CAAC,MAAM,yBAAyB,GAAG,yBAAyB,CAAC;AAoEnE,MAAM,UAAU,kBAAkB,CAChC,OAAe,EACf,SAAiB,EACjB,MAAe,EACf,WAAoB,EACpB,OAAgC;IAEhC,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;IAC1D,MAAM,eAAe,GAAG,mBAAmB,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;IAC/D,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAChC,OAAO,IAAI;QACT,WAAW,EAAE,EAAE;QACf,WAAW,EAAE,EAAE;QACf,QAAQ,EAAE,IAAI;QACd,IAAI,EAAE,OAAO;KACd,CACF,CAAC;IAEF,OAAO;iBACQ,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE;;;;wDAIU,yBAAyB;IAC7E,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,uDAAuD,mBAAmB,CAAC,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAmFlI,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA8SK,eAAe;8BACV,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA+OjC,WAAW,CAAC,CAAC,CAAC,uBAAuB,eAAe,mBAAmB,eAAe,GAAG,CAAC,CAAC,CAAC,EAAE;GACnG,OAAO;;;;;;;;;;SAUD,CAAC;AACV,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAa;IACxC,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC","sourcesContent":["const EXTENSION_IFRAME_CSP_BASE =\n \"default-src 'none'; script-src 'self' https://cdn.jsdelivr.net 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self'; img-src 'self' data: blob:; media-src 'self' data: blob:; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none';\";\n\nexport const EXTENSION_FRAME_ANCESTORS = [\n \"'self'\",\n \"https://agent-native.com\",\n \"https://*.agent-native.com\",\n \"http://localhost:*\",\n \"http://127.0.0.1:*\",\n \"https://*.claudemcpcontent.com\",\n \"https://*.web-sandbox.oaiusercontent.com\",\n].join(\" \");\n\nexport const EXTENSION_IFRAME_CSP = `${EXTENSION_IFRAME_CSP_BASE} frame-ancestors ${EXTENSION_FRAME_ANCESTORS};`;\n\nexport const EXTENSION_IFRAME_META_CSP = EXTENSION_IFRAME_CSP_BASE;\n\n/**\n * SECURITY — EXTENSION CONTENT IS UNTRUSTED.\n *\n * `${content}` (line ~Body) interpolates raw HTML/JS authored by a user. This\n * file is the boundary between framework-controlled HTML and user-controlled\n * HTML. Two non-negotiable invariants for every change here:\n *\n * 1. The iframe MUST be rendered with a `sandbox` attribute that does NOT\n * include `allow-same-origin`. The viewer (`ExtensionViewer.tsx`,\n * `EmbeddedExtension.tsx`) sets `sandbox=\"allow-scripts allow-forms\"` —\n * and that is the only acceptable shape. Adding `allow-same-origin`\n * would give the extension full DOM access to the parent window via\n * cross-frame script.\n *\n * 2. Every reachable parent action must treat the postMessage payload as\n * hostile. The bridge in `iframe-bridge.ts` enforces a path allowlist,\n * header sanitization, and method allowlist; do not relax those gates\n * for \"convenience\" in this file or any caller.\n *\n * For the trust model rationale, see audit 05-tools-sandbox.md (C1) and the\n * `extensions` skill. When in doubt, fail closed.\n *\n * BACKWARDS COMPAT — the iframe injects helpers under both their canonical\n * `extension*` names (`extensionFetch`, `extensionData`, `extensionId`,\n * `extensionBinding`) AND legacy `tool*` aliases (`toolFetch`, `toolData`,\n * `toolId`, `toolBinding`) so existing user-authored extension bodies that\n * pre-date the rename keep working. Same for layout opt-ins:\n * `data-extension-layout=\"full-bleed\"` / `data-extension-padding=\"none\"` /\n * class `agent-native-extension-bleed` / CSS var\n * `--agent-native-extension-padding` are canonical; the `data-tool-*`,\n * `agent-native-tool-bleed`, and `--agent-native-tool-padding` variants are\n * accepted as aliases.\n */\n\nexport interface ExtensionRenderBinding {\n /** Email of the user who authored / owns the extension. */\n authorEmail: string;\n /** Email of the user currently viewing/running the extension. */\n viewerEmail: string;\n /** True when viewer === author. */\n isAuthor: boolean;\n /**\n * Resolved role for the viewer (\"owner\" | \"admin\" | \"editor\" | \"viewer\").\n *\n * TODO(security, audit H4): the host-side bridge does not yet gate any\n * helper based on this value — every viewer gets the same powers as the\n * author. The role is plumbed through so a follow-up PR can constrain\n * `appAction` / `dbExec` / `extensionFetch` for non-author viewers (and\n * eventually require an explicit consent step before running a shared\n * extension, audit C1). For now this is metadata only.\n */\n role: \"owner\" | \"admin\" | \"editor\" | \"viewer\";\n /** Where the extension definition came from. Database extensions are the default. */\n source?: \"database\" | \"local-files\";\n /**\n * Fine-grained helper permissions for local file extensions. Database-backed\n * extensions keep using the role table in the parent bridge.\n */\n permissions?: {\n appActions?: string[];\n extensionData?: boolean;\n sql?: boolean;\n externalFetch?: boolean;\n };\n}\n\nexport function buildExtensionHtml(\n content: string,\n themeVars: string,\n isDark: boolean,\n extensionId?: string,\n binding?: ExtensionRenderBinding,\n): string {\n const extensionIdJson = JSON.stringify(extensionId ?? \"\");\n const extensionIdAttr = escapeHtmlAttribute(extensionId ?? \"\");\n const bindingJson = JSON.stringify(\n binding ?? {\n authorEmail: \"\",\n viewerEmail: \"\",\n isAuthor: true,\n role: \"owner\",\n },\n );\n\n return `<!DOCTYPE html>\n<html lang=\"en\"${isDark ? ' class=\"dark\"' : \"\"}>\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <meta http-equiv=\"Content-Security-Policy\" content=\"${EXTENSION_IFRAME_META_CSP}\" />\n ${binding && !binding.isAuthor ? `<meta name=\"agent-native-extension-author\" content=\"${escapeHtmlAttribute(binding.authorEmail)}\" />` : \"\"}\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300..700&display=swap\" rel=\"stylesheet\" />\n <script>\n var _extensionErrors = [];\n var _extensionErrorDetails = [];\n var _consoleLogs = [];\n var _networkLogs = [];\n\n var _origConsole = { log: console.log, warn: console.warn, error: console.error, info: console.info };\n function _wrapConsole(level, orig) {\n return function() {\n var args = Array.prototype.slice.call(arguments);\n var msg = args.map(function(a) {\n try { return typeof a === 'object' ? JSON.stringify(a) : String(a); }\n catch(e) { return String(a); }\n }).join(' ');\n if (_consoleLogs.length >= 50) _consoleLogs.shift();\n _consoleLogs.push({ level: level, message: msg });\n orig.apply(console, arguments);\n };\n }\n console.log = _wrapConsole('log', _origConsole.log);\n console.warn = _wrapConsole('warn', _origConsole.warn);\n console.error = _wrapConsole('error', _origConsole.error);\n console.info = _wrapConsole('info', _origConsole.info);\n\n function _collectError(message, stack) {\n if (!message) return;\n if (message === 'Script error.' || message === 'Script error') message = 'Runtime error';\n if (_extensionErrors.indexOf(message) !== -1) return;\n _extensionErrors.push(message);\n _extensionErrorDetails.push({ message: message, stack: stack || '' });\n _renderErrorToast();\n }\n\n function _renderErrorToast() {\n var toast = document.getElementById('__extension-error-toast');\n if (!toast) return;\n var msg = document.getElementById('__extension-error-msg');\n if (!msg || _extensionErrors.length === 0) return;\n if (_extensionErrors.length === 1) {\n msg.textContent = _extensionErrors[0];\n } else {\n msg.textContent = _extensionErrors.length + ' errors — ' + _extensionErrors[_extensionErrors.length - 1];\n }\n toast.style.display = 'block';\n }\n\n window.addEventListener('error', function(event) {\n var msg = event.message || '';\n if (msg.indexOf('Alpine Expression Error') === 0) return;\n var stack = event.error && event.error.stack ? event.error.stack : '';\n _collectError(msg, stack);\n });\n\n window.addEventListener('unhandledrejection', function(event) {\n var msg = event.reason && event.reason.message ? event.reason.message : String(event.reason);\n var stack = event.reason && event.reason.stack ? event.reason.stack : '';\n _collectError(msg, stack);\n });\n </script>\n <!--\n SECURITY: pinned to exact patch versions + SRI integrity hashes. A\n malicious republish of @tailwindcss/browser@4.x or alpinejs@3.x would\n otherwise inject code into every extension. To bump these versions:\n 1. npm view @tailwindcss/browser version (or alpinejs)\n 2. curl -sL https://cdn.jsdelivr.net/npm/@tailwindcss/browser@<v> \\\\\n | openssl dgst -sha384 -binary | openssl base64 -A\n 3. Update the URL + integrity hash below in lockstep.\n -->\n <script\n src=\"https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.2.4\"\n integrity=\"sha384-yNSZBFvuOWcmww494a9+1zNuvgUGEXoWkein7cxP8wHUTi3iXCU4vJ7hr3tzBCml\"\n crossorigin=\"anonymous\"\n ></script>\n <script\n defer\n src=\"https://cdn.jsdelivr.net/npm/alpinejs@3.15.11/dist/cdn.min.js\"\n integrity=\"sha384-WPtu0YHhJ3arcykfnv1JgUffWDSKRnqnDeTpJUbOc2os2moEmLkIdaeR0trPN4be\"\n crossorigin=\"anonymous\"\n ></script>\n <style>${themeVars}</style>\n <style type=\"text/tailwindcss\">\n @custom-variant dark (&:where(.dark, .dark *));\n @theme {\n --color-border: hsl(var(--border));\n --color-input: hsl(var(--input));\n --color-ring: hsl(var(--ring));\n --color-background: hsl(var(--background));\n --color-foreground: hsl(var(--foreground));\n --color-primary: hsl(var(--primary));\n --color-primary-foreground: hsl(var(--primary-foreground));\n --color-secondary: hsl(var(--secondary));\n --color-secondary-foreground: hsl(var(--secondary-foreground));\n --color-destructive: hsl(var(--destructive));\n --color-destructive-foreground: hsl(var(--destructive-foreground));\n --color-muted: hsl(var(--muted));\n --color-muted-foreground: hsl(var(--muted-foreground));\n --color-accent: hsl(var(--accent));\n --color-accent-foreground: hsl(var(--accent-foreground));\n --color-popover: hsl(var(--popover));\n --color-popover-foreground: hsl(var(--popover-foreground));\n --color-card: hsl(var(--card));\n --color-card-foreground: hsl(var(--card-foreground));\n --color-sidebar: hsl(var(--sidebar-background));\n --color-sidebar-foreground: hsl(var(--sidebar-foreground));\n --color-sidebar-primary: hsl(var(--sidebar-primary));\n --color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground));\n --color-sidebar-accent: hsl(var(--sidebar-accent));\n --color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground));\n --color-sidebar-border: hsl(var(--sidebar-border));\n --color-sidebar-ring: hsl(var(--sidebar-ring));\n --radius-lg: var(--radius);\n --radius-md: calc(var(--radius) - 2px);\n --radius-sm: calc(var(--radius) - 4px);\n }\n </style>\n\t <style>\n\t *, *::before, *::after { border-color: hsl(var(--border)); }\n\t body {\n\t --agent-native-extension-padding: clamp(16px, 2vw, 24px);\n\t /* Legacy alias for pre-rename extension content (do not remove). */\n\t --agent-native-tool-padding: var(--agent-native-extension-padding);\n\t box-sizing: border-box;\n\t font-family: 'Inter', sans-serif;\n\t margin: 0;\n\t min-height: 100vh;\n\t padding: var(--agent-native-extension-padding);\n\t }\n\t body:has(> [data-extension-layout=\"full-bleed\"]),\n\t body:has(> [data-extension-padding=\"none\"]),\n\t body:has(> .agent-native-extension-bleed),\n\t /* Legacy aliases (do not remove). */\n\t body:has(> [data-tool-layout=\"full-bleed\"]),\n\t body:has(> [data-tool-padding=\"none\"]),\n\t body:has(> .agent-native-tool-bleed) {\n\t padding: 0;\n\t }\n\t </style>\n\t <script>\n\t var _extensionRequestSeq = 0;\n\t var _extensionPendingRequests = {};\n\n\t window.addEventListener('message', function(event) {\n\t if (event.source !== window.parent) return;\n\t var message = event.data || {};\n\t if (\n\t message.type !== 'agent-native-extension-response' &&\n\t message.type !== 'agent-native-tool-response'\n\t ) return;\n\t var pending = _extensionPendingRequests[message.requestId];\n\t if (!pending) return;\n\t delete _extensionPendingRequests[message.requestId];\n\t if (message.error) {\n\t pending.reject(new Error(message.error));\n\t } else {\n\t pending.resolve(message.response);\n\t }\n\t });\n\n\t function hostRequest(path, options) {\n\t options = options || {};\n\t return new Promise(function(resolve, reject) {\n\t var requestId = 'extension-req-' + (++_extensionRequestSeq);\n\t _extensionPendingRequests[requestId] = { resolve: resolve, reject: reject };\n\t window.parent.postMessage({\n\t type: 'agent-native-extension-request',\n\t requestId: requestId,\n\t path: path,\n\t options: {\n\t method: options.method || 'GET',\n\t headers: options.headers || {},\n\t body: options.body,\n\t },\n\t }, '*');\n\t setTimeout(function() {\n\t var pending = _extensionPendingRequests[requestId];\n\t if (!pending) return;\n\t delete _extensionPendingRequests[requestId];\n\t pending.reject(new Error('Extension host request timed out'));\n\t }, 30000);\n\t });\n\t }\n\n\t var _origHostRequest = hostRequest;\n\t hostRequest = function(path, options) {\n\t var entry = { path: path, method: (options && options.method) || 'GET' };\n\t return _origHostRequest(path, options).then(function(res) {\n\t entry.ok = res.ok;\n\t entry.status = res.status;\n\t if (!res.ok && res.body) {\n\t try { entry.error = typeof res.body === 'string' ? res.body.slice(0, 200) : JSON.stringify(res.body).slice(0, 200); } catch(e) {}\n\t }\n\t if (_networkLogs.length >= 20) _networkLogs.shift();\n\t _networkLogs.push(entry);\n\t return res;\n\t }, function(err) {\n\t entry.ok = false;\n\t entry.error = err.message;\n\t if (_networkLogs.length >= 20) _networkLogs.shift();\n\t _networkLogs.push(entry);\n\t throw err;\n\t });\n\t };\n\n\t function extensionFetch(url, options) {\n\t var opts = options || {};\n\t return hostRequest('/_agent-native/extensions/proxy', {\n\t method: 'POST',\n\t headers: { 'Content-Type': 'application/json' },\n\t body: JSON.stringify({\n\t url: url,\n method: opts.method || 'GET',\n headers: opts.headers,\n body: opts.body,\n }),\n\t }).then(function(res) {\n\t var data = res.body;\n\t if (data.error && data.status === undefined) {\n\t throw new Error(data.error);\n\t }\n return {\n ok: data.status >= 200 && data.status < 300,\n status: data.status,\n\t json: function() { return Promise.resolve(data.body); },\n\t text: function() { return Promise.resolve(typeof data.body === 'string' ? data.body : JSON.stringify(data.body)); },\n\t };\n\t });\n\t }\n\n\t function _appendActionQuery(path, params) {\n\t var search = new URLSearchParams();\n\t params = params || {};\n\t Object.keys(params).forEach(function(key) {\n\t var value = params[key];\n\t if (value === undefined || value === null) return;\n\t if (Array.isArray(value)) {\n\t value.forEach(function(item) {\n\t if (item !== undefined && item !== null) {\n\t search.append(key, String(item));\n\t }\n\t });\n\t return;\n\t }\n\t search.set(key, String(value));\n\t });\n\t var qs = search.toString();\n\t return qs ? path + '?' + qs : path;\n\t }\n\n\t function _methodHintFromActionResponse(res) {\n\t if (!res || res.status !== 405) return null;\n\t var body = res.body || {};\n\t var message = typeof body === 'string' ? body : body.error;\n\t if (!message) return null;\n\t var match = String(message).match(/Use (GET|POST|PUT|PATCH|DELETE|HEAD)\\\\.?/i);\n\t return match ? match[1].toUpperCase() : null;\n\t }\n\n\t async function appAction(name, params) {\n\t params = params || {};\n\t if (name === 'navigate') {\n\t var navRes = await hostRequest('/_agent-native/application-state/navigate', {\n\t method: 'PUT',\n\t headers: { 'Content-Type': 'application/json' },\n\t body: JSON.stringify(params),\n\t });\n\t if (!navRes.ok) {\n\t var navErr = navRes.body || { error: navRes.statusText };\n\t throw new Error(navErr.error || 'Navigation failed: ' + navRes.status);\n\t }\n\t return navRes.body;\n\t }\n\t var path = '/_agent-native/actions/' + encodeURIComponent(name);\n\t var res = await hostRequest(path, {\n\t method: 'POST',\n\t headers: { 'Content-Type': 'application/json' },\n\t body: JSON.stringify(params),\n\t });\n\n\t var retryMethod = _methodHintFromActionResponse(res);\n\t if (!res.ok && retryMethod && retryMethod !== 'POST') {\n\t var retryPath = path;\n\t var retryOptions = {\n\t method: retryMethod,\n\t headers: { 'Content-Type': 'application/json' },\n\t };\n\t if (retryMethod === 'GET' || retryMethod === 'HEAD') {\n\t retryPath = _appendActionQuery(path, params);\n\t } else {\n\t retryOptions.body = JSON.stringify(params);\n\t }\n\t res = await hostRequest(retryPath, retryOptions);\n\t }\n\n\t if (!res.ok) {\n\t var err = res.body || { error: res.statusText };\n\t throw new Error(err.error || 'Action failed: ' + res.status);\n\t }\n\t return res.body;\n\t }\n\n\t async function appFetch(path, options) {\n\t options = options || {};\n\t var res = await hostRequest(path, {\n\t ...options,\n\t headers: {\n\t 'Content-Type': 'application/json',\n\t ...(options.headers || {}),\n\t },\n\t });\n\t if (!res.ok) {\n\t var err = typeof res.body === 'object' && res.body ? res.body : { error: res.statusText };\n\t throw new Error(err.error || 'Request failed: ' + res.status);\n\t }\n\t return res.body;\n\t }\n\n\t function sendToChat(message, options) {\n\t options = options || {};\n\t var text = typeof message === 'string' ? message : JSON.stringify(message);\n\t window.parent.postMessage({\n\t type: 'agent-native-send-to-chat',\n\t message: text,\n\t context: options.context,\n\t submit: options.submit !== false,\n\t openSidebar: options.openSidebar !== false,\n\t }, '*');\n\t return { ok: true };\n\t }\n\n\t function inlineUiOutputKey() {\n\t var safeId = String(_extensionId || 'unknown').replace(/[^A-Za-z0-9_:-]/g, '') || 'unknown';\n\t return 'inline-ui:' + safeId + ':output';\n\t }\n\n\t async function outputToUi(value, options) {\n\t options = options || {};\n\t var key = inlineUiOutputKey();\n\t var payload = {\n\t value: value,\n\t updatedAt: new Date().toISOString(),\n\t extensionId: _extensionId,\n\t source: 'inline-ui',\n\t };\n\t if (options.label !== undefined) payload.label = options.label;\n\t if (options.context !== undefined) payload.context = options.context;\n\t if (options.meta !== undefined) payload.meta = options.meta;\n\t var output = await appFetch('/_agent-native/application-state/' + key, {\n\t method: 'PUT',\n\t headers: { 'X-Request-Source': 'inline-ui' },\n\t body: JSON.stringify(payload),\n\t });\n\t try {\n\t window.parent.postMessage({\n\t type: 'agent-native-ui-output',\n\t extensionId: _extensionId,\n\t key: key,\n\t value: value,\n\t output: output,\n\t }, '*');\n\t } catch (_) {}\n\t return { ok: true, key: key, output: output };\n\t }\n\n async function dbQuery(sql, args) {\n var body = { sql: sql };\n if (args) body.args = args;\n return appFetch('/_agent-native/extensions/sql/query', {\n method: 'POST',\n body: JSON.stringify(body),\n });\n }\n\n async function dbExec(sql, args) {\n var body = { sql: sql };\n if (args) body.args = args;\n return appFetch('/_agent-native/extensions/sql/exec', {\n method: 'POST',\n body: JSON.stringify(body),\n });\n }\n\n var _extensionId = ${extensionIdJson};\n var _extensionBinding = ${bindingJson};\n window.extensionBinding = _extensionBinding;\n // Legacy alias for extension bodies authored before the rename.\n window.toolBinding = _extensionBinding;\n // SECURITY (audit H4): announce the resolved binding to the parent so the\n // host bridge can gate dangerous helpers based on viewer role. Sent\n // BEFORE the user-authored content has a chance to run, so a malicious\n // extension body cannot suppress or rewrite the announcement. The parent\n // ignores subsequent announcements for the same iframe; see\n // ExtensionViewer.tsx / EmbeddedExtension.tsx.\n try {\n window.parent.postMessage(\n {\n type: 'agent-native-extension-binding',\n extensionId: _extensionId,\n binding: _extensionBinding,\n },\n '*',\n );\n } catch (_) {}\n // SECURITY: when the viewer is not the author of this extension, emit a\n // clear console warning. The bridge currently runs every helper with the\n // viewer's session — a malicious shared extension can call any action,\n // read any owned table row in scope, and resolve any user-scope secret.\n // A full consent step is tracked as TODO C1 in audit 05-tools-sandbox.md.\n if (_extensionBinding && !_extensionBinding.isAuthor) {\n try {\n console.warn(\n '[agent-native] Shared extension — running with viewer\\\\'s session. ' +\n 'Author: ' + (_extensionBinding.authorEmail || '<unknown>') + '. ' +\n 'Bridge calls (appAction, dbExec, extensionFetch) execute under ' +\n 'your account; they are gated by your permissions, not the ' +\n 'author\\\\'s. Do not run untrusted shared extensions.',\n );\n } catch (_) {}\n }\n\n var extensionData = {\n\t async list(collection, opts) {\n\t var limit = (opts && opts.limit) || 100;\n\t var scope = (opts && opts.scope) || 'user';\n\t var res = await hostRequest('/_agent-native/extensions/data/' + _extensionId + '/' + encodeURIComponent(collection) + '?limit=' + limit + '&scope=' + scope);\n\t if (!res.ok) throw new Error('Failed to list extension data');\n\t return res.body;\n\t },\n async get(collection, id, opts) {\n var scope = (opts && opts.scope) || 'user';\n var items = await this.list(collection, { scope: scope });\n return (items || []).find(function(item) { return item.id === id; }) || null;\n },\n async set(collection, id, data, opts) {\n\t var scope = (opts && opts.scope) || 'user';\n\t var res = await hostRequest('/_agent-native/extensions/data/' + _extensionId + '/' + encodeURIComponent(collection), {\n\t method: 'POST',\n\t headers: { 'Content-Type': 'application/json' },\n\t body: JSON.stringify({ id: id, data: data, scope: scope }),\n\t });\n\t if (!res.ok) throw new Error('Failed to save extension data');\n\t return res.body;\n\t },\n\t async remove(collection, id, opts) {\n\t var scope = (opts && opts.scope) || 'user';\n\t var res = await hostRequest('/_agent-native/extensions/data/' + _extensionId + '/' + encodeURIComponent(collection) + '/' + encodeURIComponent(id) + '?scope=' + scope, {\n\t method: 'DELETE',\n\t });\n\t if (!res.ok) throw new Error('Failed to delete extension data');\n\t return res.body;\n\t },\n\t };\n\n\t // Legacy aliases — extension bodies authored before the rename use\n\t // toolFetch, toolData, toolId. Keep these working forever.\n\t var toolFetch = extensionFetch;\n\t var toolData = extensionData;\n\t var _toolId = _extensionId;\n\t window.agentNative = Object.assign(window.agentNative || {}, {\n\t extensionId: _extensionId,\n\t extensionBinding: _extensionBinding,\n\t appAction: appAction,\n\t appFetch: appFetch,\n\t dbQuery: dbQuery,\n\t dbExec: dbExec,\n\t extensionFetch: extensionFetch,\n\t extensionData: extensionData,\n\t data: extensionData,\n\t sendToChat: sendToChat,\n\t chat: Object.assign({}, (window.agentNative && window.agentNative.chat) || {}, {\n\t send: sendToChat,\n\t }),\n\t ui: Object.assign({}, (window.agentNative && window.agentNative.ui) || {}, {\n\t output: outputToUi,\n\t }),\n\t });\n\t window.sendToAgentChat = sendToChat;\n\t </script>\n\t <style>\n\t #__extension-error-toast {\n\t display: none;\n\t position: fixed;\n\t bottom: 16px;\n\t right: 16px;\n\t max-width: 420px;\n\t background: hsl(var(--destructive));\n\t color: hsl(var(--destructive-foreground));\n\t border: 1px solid hsl(var(--destructive) / .6);\n\t border-radius: calc(var(--radius, .5rem) + 2px);\n\t padding: 12px 16px;\n\t font-size: 13px;\n\t line-height: 1.4;\n\t font-family: 'Inter', sans-serif;\n\t z-index: 9999;\n\t box-shadow: 0 4px 12px rgba(0,0,0,.15), 0 1px 3px rgba(0,0,0,.1);\n\t animation: __toast-in 0.2s ease-out;\n\t }\n\t @keyframes __toast-in {\n\t from { opacity: 0; transform: translateY(8px); }\n\t to { opacity: 1; transform: translateY(0); }\n\t }\n\t </style>\n\t <script>\n\t // Extension-point slot context: when an extension is rendered embedded\n\t // inside an ExtensionSlot, the host pushes a context object via\n\t // postMessage. Extensions read it synchronously via window.slotContext\n\t // or subscribe to changes via window.onSlotContext(fn). When rendered\n\t // full-page (no ?slot= param), slotContext stays null and extensions\n\t // branch on that.\n\t window.slotContext = null;\n\t var _slotContextSubscribers = [];\n\t window.onSlotContext = function(fn) {\n\t _slotContextSubscribers.push(fn);\n\t if (window.slotContext !== null) {\n\t try { fn(window.slotContext); } catch(_) {}\n\t }\n\t return function() {\n\t _slotContextSubscribers = _slotContextSubscribers.filter(function(f) { return f !== fn; });\n\t };\n\t };\n\t window.addEventListener('message', function(event) {\n\t if (event.source !== window.parent) return;\n\t var msg = event.data;\n\t if (!msg || msg.type !== 'agent-native-slot-context') return;\n\t window.slotContext = msg.context || {};\n\t _slotContextSubscribers.forEach(function(fn) {\n\t try { fn(window.slotContext); } catch(_) {}\n\t });\n\t });\n\n\t // Auto-resize iframe renders. Persisted extension slots include ?slot=;\n\t // transient inline chat UI uses srcdoc, so detect that by parent frame.\n\t // The host listens for agent-native-extension-resize and adjusts height.\n\t if (new URLSearchParams(location.search).get('slot') || window.parent !== window) {\n\t var _lastH = 0;\n\t var _reportHeight = function() {\n\t var h = Math.max(\n\t document.documentElement.scrollHeight,\n\t document.body ? document.body.scrollHeight : 0,\n\t );\n\t if (h !== _lastH) {\n\t _lastH = h;\n\t window.parent.postMessage({ type: 'agent-native-extension-resize', height: h }, '*');\n\t }\n\t };\n\t if (typeof ResizeObserver !== 'undefined') {\n\t var _ro = new ResizeObserver(_reportHeight);\n\t document.addEventListener('DOMContentLoaded', function() {\n\t _ro.observe(document.documentElement);\n\t if (document.body) _ro.observe(document.body);\n\t });\n\t }\n\t // Initial reports — Alpine takes a tick to render after DOMContentLoaded.\n\t setTimeout(_reportHeight, 50);\n\t setTimeout(_reportHeight, 250);\n\t }\n\n\t window.addEventListener('message', function(event) {\n\t if (event.source !== window.parent) return;\n\t var msg = event.data;\n\t if (!msg || msg.type !== 'agent-native-theme-update') return;\n\t var root = document.documentElement;\n\t if (msg.isDark !== undefined) {\n\t if (msg.isDark) root.classList.add('dark');\n\t else root.classList.remove('dark');\n\t }\n\t var vars = msg.vars || {};\n\t for (var key in vars) {\n\t if (vars.hasOwnProperty(key)) {\n\t root.style.setProperty(key, vars[key]);\n\t }\n\t }\n\t });\n\n\t document.addEventListener('keydown', function(e) {\n\t if ((e.metaKey || e.ctrlKey) && !e.altKey) {\n\t var key = (e.key || '').toLowerCase();\n\t if (key === 'c' || key === 'v' || key === 'x' || key === 'a' || key === 'z' || key === 'y') return;\n\t e.preventDefault();\n\t e.stopPropagation();\n\t window.parent.postMessage({\n\t type: 'agent-native-extension-keydown',\n\t key: e.key, code: e.code,\n\t metaKey: e.metaKey, ctrlKey: e.ctrlKey,\n\t shiftKey: e.shiftKey, altKey: e.altKey,\n\t }, '*');\n\t return;\n\t }\n\t if (e.key === 'Escape') {\n\t window.parent.postMessage({\n\t type: 'agent-native-extension-keydown',\n\t key: e.key, code: e.code,\n\t metaKey: false, ctrlKey: false,\n\t shiftKey: false, altKey: false,\n\t }, '*');\n\t }\n\t });\n\n\t document.addEventListener('DOMContentLoaded', function() {\n\t _renderErrorToast();\n\t var fixBtn = document.getElementById('__extension-error-fix');\n\t if (fixBtn) {\n\t fixBtn.addEventListener('click', function() {\n\t window.parent.postMessage({\n\t type: 'agent-native-extension-error-fix',\n\t errors: _extensionErrors,\n\t errorDetails: _extensionErrorDetails,\n\t consoleLogs: _consoleLogs.slice(-30),\n\t networkLogs: _networkLogs.slice(-15)\n\t }, '*');\n\t document.getElementById('__extension-error-toast').style.display = 'none';\n\t });\n\t }\n\t var dismissBtn = document.getElementById('__extension-error-dismiss');\n\t if (dismissBtn) {\n\t dismissBtn.addEventListener('click', function() {\n\t document.getElementById('__extension-error-toast').style.display = 'none';\n\t });\n\t }\n\t });\n\t </script>\n\t</head>\n\t<body${extensionId ? ` data-extension-id=\"${extensionIdAttr}\" data-tool-id=\"${extensionIdAttr}\"` : \"\"} class=\"bg-background text-foreground\">\n\t${content}\n\t<div id=\"__extension-error-toast\">\n\t <div style=\"display:flex;align-items:flex-start;gap:8px;\">\n\t <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"flex-shrink:0;margin-top:1px;\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\"/><line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\"/></svg>\n\t <span id=\"__extension-error-msg\" style=\"flex:1;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;\"></span>\n\t <button id=\"__extension-error-fix\" style=\"cursor:pointer;border:none;background:rgba(255,255,255,.9);color:hsl(0 84.2% 40%);font-size:12px;font-weight:500;padding:4px 12px;border-radius:4px;flex-shrink:0;\">Fix</button>\n\t <button id=\"__extension-error-dismiss\" style=\"cursor:pointer;border:none;background:transparent;color:inherit;font-size:16px;padding:2px 6px;opacity:0.7;flex-shrink:0;\">×</button>\n\t </div>\n\t</div>\n\t</body>\n\t</html>`;\n}\n\nfunction escapeHtmlAttribute(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n"]}
|
|
1
|
+
{"version":3,"file":"html-shell.js","sourceRoot":"","sources":["../../src/extensions/html-shell.ts"],"names":[],"mappings":"AAAA,MAAM,yBAAyB,GAC7B,mXAAmX,CAAC;AAEtX,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,QAAQ;IACR,0BAA0B;IAC1B,4BAA4B;IAC5B,oBAAoB;IACpB,oBAAoB;IACpB,gCAAgC;IAChC,0CAA0C;CAC3C,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAEZ,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,yBAAyB,oBAAoB,yBAAyB,GAAG,CAAC;AAEjH,MAAM,CAAC,MAAM,yBAAyB,GAAG,yBAAyB,CAAC;AAoEnE,MAAM,UAAU,kBAAkB,CAChC,OAAe,EACf,SAAiB,EACjB,MAAe,EACf,WAAoB,EACpB,OAAgC;IAEhC,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;IAC1D,MAAM,eAAe,GAAG,mBAAmB,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;IAC/D,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAChC,OAAO,IAAI;QACT,WAAW,EAAE,EAAE;QACf,WAAW,EAAE,EAAE;QACf,QAAQ,EAAE,IAAI;QACd,IAAI,EAAE,OAAO;KACd,CACF,CAAC;IAEF,OAAO;iBACQ,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE;;;;wDAIU,yBAAyB;IAC7E,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,uDAAuD,mBAAmB,CAAC,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAmFlI,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA4TK,eAAe;8BACV,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA+OjC,WAAW,CAAC,CAAC,CAAC,uBAAuB,eAAe,mBAAmB,eAAe,GAAG,CAAC,CAAC,CAAC,EAAE;GACnG,OAAO;;;;;;;;;;SAUD,CAAC;AACV,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAa;IACxC,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC","sourcesContent":["const EXTENSION_IFRAME_CSP_BASE =\n \"default-src 'none'; script-src 'self' https://cdn.jsdelivr.net 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self'; img-src 'self' data: blob:; media-src 'self' data: blob:; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none';\";\n\nexport const EXTENSION_FRAME_ANCESTORS = [\n \"'self'\",\n \"https://agent-native.com\",\n \"https://*.agent-native.com\",\n \"http://localhost:*\",\n \"http://127.0.0.1:*\",\n \"https://*.claudemcpcontent.com\",\n \"https://*.web-sandbox.oaiusercontent.com\",\n].join(\" \");\n\nexport const EXTENSION_IFRAME_CSP = `${EXTENSION_IFRAME_CSP_BASE} frame-ancestors ${EXTENSION_FRAME_ANCESTORS};`;\n\nexport const EXTENSION_IFRAME_META_CSP = EXTENSION_IFRAME_CSP_BASE;\n\n/**\n * SECURITY — EXTENSION CONTENT IS UNTRUSTED.\n *\n * `${content}` (line ~Body) interpolates raw HTML/JS authored by a user. This\n * file is the boundary between framework-controlled HTML and user-controlled\n * HTML. Two non-negotiable invariants for every change here:\n *\n * 1. The iframe MUST be rendered with a `sandbox` attribute that does NOT\n * include `allow-same-origin`. The viewer (`ExtensionViewer.tsx`,\n * `EmbeddedExtension.tsx`) sets `sandbox=\"allow-scripts allow-forms\"` —\n * and that is the only acceptable shape. Adding `allow-same-origin`\n * would give the extension full DOM access to the parent window via\n * cross-frame script.\n *\n * 2. Every reachable parent action must treat the postMessage payload as\n * hostile. The bridge in `iframe-bridge.ts` enforces a path allowlist,\n * header sanitization, and method allowlist; do not relax those gates\n * for \"convenience\" in this file or any caller.\n *\n * For the trust model rationale, see audit 05-tools-sandbox.md (C1) and the\n * `extensions` skill. When in doubt, fail closed.\n *\n * BACKWARDS COMPAT — the iframe injects helpers under both their canonical\n * `extension*` names (`extensionFetch`, `extensionData`, `extensionId`,\n * `extensionBinding`) AND legacy `tool*` aliases (`toolFetch`, `toolData`,\n * `toolId`, `toolBinding`) so existing user-authored extension bodies that\n * pre-date the rename keep working. Same for layout opt-ins:\n * `data-extension-layout=\"full-bleed\"` / `data-extension-padding=\"none\"` /\n * class `agent-native-extension-bleed` / CSS var\n * `--agent-native-extension-padding` are canonical; the `data-tool-*`,\n * `agent-native-tool-bleed`, and `--agent-native-tool-padding` variants are\n * accepted as aliases.\n */\n\nexport interface ExtensionRenderBinding {\n /** Email of the user who authored / owns the extension. */\n authorEmail: string;\n /** Email of the user currently viewing/running the extension. */\n viewerEmail: string;\n /** True when viewer === author. */\n isAuthor: boolean;\n /**\n * Resolved role for the viewer (\"owner\" | \"admin\" | \"editor\" | \"viewer\").\n *\n * TODO(security, audit H4): the host-side bridge does not yet gate any\n * helper based on this value — every viewer gets the same powers as the\n * author. The role is plumbed through so a follow-up PR can constrain\n * `appAction` / `dbExec` / `extensionFetch` for non-author viewers (and\n * eventually require an explicit consent step before running a shared\n * extension, audit C1). For now this is metadata only.\n */\n role: \"owner\" | \"admin\" | \"editor\" | \"viewer\";\n /** Where the extension definition came from. Database extensions are the default. */\n source?: \"database\" | \"local-files\";\n /**\n * Fine-grained helper permissions for local file extensions. Database-backed\n * extensions keep using the role table in the parent bridge.\n */\n permissions?: {\n appActions?: string[];\n extensionData?: boolean;\n sql?: boolean;\n externalFetch?: boolean;\n };\n}\n\nexport function buildExtensionHtml(\n content: string,\n themeVars: string,\n isDark: boolean,\n extensionId?: string,\n binding?: ExtensionRenderBinding,\n): string {\n const extensionIdJson = JSON.stringify(extensionId ?? \"\");\n const extensionIdAttr = escapeHtmlAttribute(extensionId ?? \"\");\n const bindingJson = JSON.stringify(\n binding ?? {\n authorEmail: \"\",\n viewerEmail: \"\",\n isAuthor: true,\n role: \"owner\",\n },\n );\n\n return `<!DOCTYPE html>\n<html lang=\"en\"${isDark ? ' class=\"dark\"' : \"\"}>\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <meta http-equiv=\"Content-Security-Policy\" content=\"${EXTENSION_IFRAME_META_CSP}\" />\n ${binding && !binding.isAuthor ? `<meta name=\"agent-native-extension-author\" content=\"${escapeHtmlAttribute(binding.authorEmail)}\" />` : \"\"}\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300..700&display=swap\" rel=\"stylesheet\" />\n <script>\n var _extensionErrors = [];\n var _extensionErrorDetails = [];\n var _consoleLogs = [];\n var _networkLogs = [];\n\n var _origConsole = { log: console.log, warn: console.warn, error: console.error, info: console.info };\n function _wrapConsole(level, orig) {\n return function() {\n var args = Array.prototype.slice.call(arguments);\n var msg = args.map(function(a) {\n try { return typeof a === 'object' ? JSON.stringify(a) : String(a); }\n catch(e) { return String(a); }\n }).join(' ');\n if (_consoleLogs.length >= 50) _consoleLogs.shift();\n _consoleLogs.push({ level: level, message: msg });\n orig.apply(console, arguments);\n };\n }\n console.log = _wrapConsole('log', _origConsole.log);\n console.warn = _wrapConsole('warn', _origConsole.warn);\n console.error = _wrapConsole('error', _origConsole.error);\n console.info = _wrapConsole('info', _origConsole.info);\n\n function _collectError(message, stack) {\n if (!message) return;\n if (message === 'Script error.' || message === 'Script error') message = 'Runtime error';\n if (_extensionErrors.indexOf(message) !== -1) return;\n _extensionErrors.push(message);\n _extensionErrorDetails.push({ message: message, stack: stack || '' });\n _renderErrorToast();\n }\n\n function _renderErrorToast() {\n var toast = document.getElementById('__extension-error-toast');\n if (!toast) return;\n var msg = document.getElementById('__extension-error-msg');\n if (!msg || _extensionErrors.length === 0) return;\n if (_extensionErrors.length === 1) {\n msg.textContent = _extensionErrors[0];\n } else {\n msg.textContent = _extensionErrors.length + ' errors — ' + _extensionErrors[_extensionErrors.length - 1];\n }\n toast.style.display = 'block';\n }\n\n window.addEventListener('error', function(event) {\n var msg = event.message || '';\n if (msg.indexOf('Alpine Expression Error') === 0) return;\n var stack = event.error && event.error.stack ? event.error.stack : '';\n _collectError(msg, stack);\n });\n\n window.addEventListener('unhandledrejection', function(event) {\n var msg = event.reason && event.reason.message ? event.reason.message : String(event.reason);\n var stack = event.reason && event.reason.stack ? event.reason.stack : '';\n _collectError(msg, stack);\n });\n </script>\n <!--\n SECURITY: pinned to exact patch versions + SRI integrity hashes. A\n malicious republish of @tailwindcss/browser@4.x or alpinejs@3.x would\n otherwise inject code into every extension. To bump these versions:\n 1. npm view @tailwindcss/browser version (or alpinejs)\n 2. curl -sL https://cdn.jsdelivr.net/npm/@tailwindcss/browser@<v> \\\\\n | openssl dgst -sha384 -binary | openssl base64 -A\n 3. Update the URL + integrity hash below in lockstep.\n -->\n <script\n src=\"https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.2.4\"\n integrity=\"sha384-yNSZBFvuOWcmww494a9+1zNuvgUGEXoWkein7cxP8wHUTi3iXCU4vJ7hr3tzBCml\"\n crossorigin=\"anonymous\"\n ></script>\n <script\n defer\n src=\"https://cdn.jsdelivr.net/npm/alpinejs@3.15.11/dist/cdn.min.js\"\n integrity=\"sha384-WPtu0YHhJ3arcykfnv1JgUffWDSKRnqnDeTpJUbOc2os2moEmLkIdaeR0trPN4be\"\n crossorigin=\"anonymous\"\n ></script>\n <style>${themeVars}</style>\n <style type=\"text/tailwindcss\">\n @custom-variant dark (&:where(.dark, .dark *));\n @theme {\n --color-border: hsl(var(--border));\n --color-input: hsl(var(--input));\n --color-ring: hsl(var(--ring));\n --color-background: hsl(var(--background));\n --color-foreground: hsl(var(--foreground));\n --color-primary: hsl(var(--primary));\n --color-primary-foreground: hsl(var(--primary-foreground));\n --color-secondary: hsl(var(--secondary));\n --color-secondary-foreground: hsl(var(--secondary-foreground));\n --color-destructive: hsl(var(--destructive));\n --color-destructive-foreground: hsl(var(--destructive-foreground));\n --color-muted: hsl(var(--muted));\n --color-muted-foreground: hsl(var(--muted-foreground));\n --color-accent: hsl(var(--accent));\n --color-accent-foreground: hsl(var(--accent-foreground));\n --color-popover: hsl(var(--popover));\n --color-popover-foreground: hsl(var(--popover-foreground));\n --color-card: hsl(var(--card));\n --color-card-foreground: hsl(var(--card-foreground));\n --color-sidebar: hsl(var(--sidebar-background));\n --color-sidebar-foreground: hsl(var(--sidebar-foreground));\n --color-sidebar-primary: hsl(var(--sidebar-primary));\n --color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground));\n --color-sidebar-accent: hsl(var(--sidebar-accent));\n --color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground));\n --color-sidebar-border: hsl(var(--sidebar-border));\n --color-sidebar-ring: hsl(var(--sidebar-ring));\n --radius-lg: var(--radius);\n --radius-md: calc(var(--radius) - 2px);\n --radius-sm: calc(var(--radius) - 4px);\n }\n </style>\n\t <style>\n\t *, *::before, *::after { border-color: hsl(var(--border)); }\n\t html, body {\n\t /* Transparent so the iframe inherits the host surface (dashboard panel,\n\t sidebar, chat) instead of painting the browser's default white canvas.\n\t The dark class still flips the theme vars; content paints its own\n\t bg-background / bg-card surfaces. */\n\t background: transparent;\n\t }\n\t body {\n\t --agent-native-extension-padding: clamp(16px, 2vw, 24px);\n\t /* Legacy alias for pre-rename extension content (do not remove). */\n\t --agent-native-tool-padding: var(--agent-native-extension-padding);\n\t box-sizing: border-box;\n\t color: hsl(var(--foreground));\n\t font-family: 'Inter', sans-serif;\n\t margin: 0;\n\t min-height: 100vh;\n\t padding: var(--agent-native-extension-padding);\n\t }\n\t body:has(> [data-extension-layout=\"full-bleed\"]),\n\t body:has(> [data-extension-padding=\"none\"]),\n\t body:has(> .agent-native-extension-bleed),\n\t /* Legacy aliases (do not remove). */\n\t body:has(> [data-tool-layout=\"full-bleed\"]),\n\t body:has(> [data-tool-padding=\"none\"]),\n\t body:has(> .agent-native-tool-bleed) {\n\t padding: 0;\n\t }\n\t </style>\n\t <script>\n\t var _extensionRequestSeq = 0;\n\t var _extensionPendingRequests = {};\n\n\t window.addEventListener('message', function(event) {\n\t if (event.source !== window.parent) return;\n\t var message = event.data || {};\n\t if (\n\t message.type !== 'agent-native-extension-response' &&\n\t message.type !== 'agent-native-tool-response'\n\t ) return;\n\t var pending = _extensionPendingRequests[message.requestId];\n\t if (!pending) return;\n\t delete _extensionPendingRequests[message.requestId];\n\t if (message.error) {\n\t pending.reject(new Error(message.error));\n\t } else {\n\t pending.resolve(message.response);\n\t }\n\t });\n\n\t function hostRequest(path, options) {\n\t options = options || {};\n\t return new Promise(function(resolve, reject) {\n\t var requestId = 'extension-req-' + (++_extensionRequestSeq);\n\t _extensionPendingRequests[requestId] = { resolve: resolve, reject: reject };\n\t window.parent.postMessage({\n\t type: 'agent-native-extension-request',\n\t requestId: requestId,\n\t path: path,\n\t options: {\n\t method: options.method || 'GET',\n\t headers: options.headers || {},\n\t body: options.body,\n\t },\n\t }, '*');\n\t setTimeout(function() {\n\t var pending = _extensionPendingRequests[requestId];\n\t if (!pending) return;\n\t delete _extensionPendingRequests[requestId];\n\t pending.reject(new Error('Extension host request timed out'));\n\t }, 30000);\n\t });\n\t }\n\n\t var _origHostRequest = hostRequest;\n\t hostRequest = function(path, options) {\n\t var entry = { path: path, method: (options && options.method) || 'GET' };\n\t return _origHostRequest(path, options).then(function(res) {\n\t entry.ok = res.ok;\n\t entry.status = res.status;\n\t if (!res.ok && res.body) {\n\t try { entry.error = typeof res.body === 'string' ? res.body.slice(0, 200) : JSON.stringify(res.body).slice(0, 200); } catch(e) {}\n\t }\n\t if (_networkLogs.length >= 20) _networkLogs.shift();\n\t _networkLogs.push(entry);\n\t return res;\n\t }, function(err) {\n\t entry.ok = false;\n\t entry.error = err.message;\n\t if (_networkLogs.length >= 20) _networkLogs.shift();\n\t _networkLogs.push(entry);\n\t throw err;\n\t });\n\t };\n\n\t function extensionFetch(url, options) {\n\t var opts = options || {};\n\t return hostRequest('/_agent-native/extensions/proxy', {\n\t method: 'POST',\n\t headers: { 'Content-Type': 'application/json' },\n\t body: JSON.stringify({\n\t url: url,\n method: opts.method || 'GET',\n headers: opts.headers,\n body: opts.body,\n }),\n\t }).then(function(res) {\n\t var data = res.body;\n\t if (data.error && data.status === undefined) {\n\t throw new Error(data.error);\n\t }\n return {\n ok: data.status >= 200 && data.status < 300,\n status: data.status,\n\t json: function() { return Promise.resolve(data.body); },\n\t text: function() { return Promise.resolve(typeof data.body === 'string' ? data.body : JSON.stringify(data.body)); },\n\t };\n\t });\n\t }\n\n\t function _appendActionQuery(path, params) {\n\t var search = new URLSearchParams();\n\t params = params || {};\n\t Object.keys(params).forEach(function(key) {\n\t var value = params[key];\n\t if (value === undefined || value === null) return;\n\t if (Array.isArray(value)) {\n\t value.forEach(function(item) {\n\t if (item !== undefined && item !== null) {\n\t search.append(key, String(item));\n\t }\n\t });\n\t return;\n\t }\n\t search.set(key, String(value));\n\t });\n\t var qs = search.toString();\n\t return qs ? path + '?' + qs : path;\n\t }\n\n\t function _methodHintFromActionResponse(res) {\n\t if (!res || res.status !== 405) return null;\n\t var body = res.body || {};\n\t var message = typeof body === 'string' ? body : body.error;\n\t if (!message) return null;\n\t var match = String(message).match(/Use (GET|POST|PUT|PATCH|DELETE|HEAD)\\\\.?/i);\n\t return match ? match[1].toUpperCase() : null;\n\t }\n\n\t async function appAction(name, params) {\n\t params = params || {};\n\t if (name === 'navigate') {\n\t var navRes = await hostRequest('/_agent-native/application-state/navigate', {\n\t method: 'PUT',\n\t headers: { 'Content-Type': 'application/json' },\n\t body: JSON.stringify(params),\n\t });\n\t if (!navRes.ok) {\n\t var navErr = navRes.body || { error: navRes.statusText };\n\t throw new Error(navErr.error || 'Navigation failed: ' + navRes.status);\n\t }\n\t return navRes.body;\n\t }\n\t var path = '/_agent-native/actions/' + encodeURIComponent(name);\n\t var res = await hostRequest(path, {\n\t method: 'POST',\n\t headers: { 'Content-Type': 'application/json' },\n\t body: JSON.stringify(params),\n\t });\n\n\t var retryMethod = _methodHintFromActionResponse(res);\n\t if (!res.ok && retryMethod && retryMethod !== 'POST') {\n\t var retryPath = path;\n\t var retryOptions = {\n\t method: retryMethod,\n\t headers: { 'Content-Type': 'application/json' },\n\t };\n\t if (retryMethod === 'GET' || retryMethod === 'HEAD') {\n\t retryPath = _appendActionQuery(path, params);\n\t } else {\n\t retryOptions.body = JSON.stringify(params);\n\t }\n\t res = await hostRequest(retryPath, retryOptions);\n\t }\n\n\t if (!res.ok) {\n\t var err = res.body || {};\n\t var rawError = typeof err === 'string' ? err : err.error;\n\t var message = (typeof rawError === 'string' && rawError.trim())\n\t ? rawError\n\t : (res.status === 404\n\t ? \"Action '\" + name + \"' is not available over HTTP (404). It may be agent-only (http: false); expose it with an HTTP-mounted action to call it from an extension.\"\n\t : \"Action '\" + name + \"' failed (\" + (res.status || 'network error') + \")\");\n\t throw new Error(message);\n\t }\n\t return res.body;\n\t }\n\n\t async function appFetch(path, options) {\n\t options = options || {};\n\t var res = await hostRequest(path, {\n\t ...options,\n\t headers: {\n\t 'Content-Type': 'application/json',\n\t ...(options.headers || {}),\n\t },\n\t });\n\t if (!res.ok) {\n\t var err = typeof res.body === 'object' && res.body ? res.body : { error: res.statusText };\n\t throw new Error(err.error || 'Request failed: ' + res.status);\n\t }\n\t return res.body;\n\t }\n\n\t function sendToChat(message, options) {\n\t options = options || {};\n\t var text = typeof message === 'string' ? message : JSON.stringify(message);\n\t window.parent.postMessage({\n\t type: 'agent-native-send-to-chat',\n\t message: text,\n\t context: options.context,\n\t submit: options.submit !== false,\n\t openSidebar: options.openSidebar !== false,\n\t }, '*');\n\t return { ok: true };\n\t }\n\n\t function inlineUiOutputKey() {\n\t var safeId = String(_extensionId || 'unknown').replace(/[^A-Za-z0-9_:-]/g, '') || 'unknown';\n\t return 'inline-ui:' + safeId + ':output';\n\t }\n\n\t async function outputToUi(value, options) {\n\t options = options || {};\n\t var key = inlineUiOutputKey();\n\t var payload = {\n\t value: value,\n\t updatedAt: new Date().toISOString(),\n\t extensionId: _extensionId,\n\t source: 'inline-ui',\n\t };\n\t if (options.label !== undefined) payload.label = options.label;\n\t if (options.context !== undefined) payload.context = options.context;\n\t if (options.meta !== undefined) payload.meta = options.meta;\n\t var output = await appFetch('/_agent-native/application-state/' + key, {\n\t method: 'PUT',\n\t headers: { 'X-Request-Source': 'inline-ui' },\n\t body: JSON.stringify(payload),\n\t });\n\t try {\n\t window.parent.postMessage({\n\t type: 'agent-native-ui-output',\n\t extensionId: _extensionId,\n\t key: key,\n\t value: value,\n\t output: output,\n\t }, '*');\n\t } catch (_) {}\n\t return { ok: true, key: key, output: output };\n\t }\n\n async function dbQuery(sql, args) {\n var body = { sql: sql };\n if (args) body.args = args;\n return appFetch('/_agent-native/extensions/sql/query', {\n method: 'POST',\n body: JSON.stringify(body),\n });\n }\n\n async function dbExec(sql, args) {\n var body = { sql: sql };\n if (args) body.args = args;\n return appFetch('/_agent-native/extensions/sql/exec', {\n method: 'POST',\n body: JSON.stringify(body),\n });\n }\n\n var _extensionId = ${extensionIdJson};\n var _extensionBinding = ${bindingJson};\n window.extensionBinding = _extensionBinding;\n // Legacy alias for extension bodies authored before the rename.\n window.toolBinding = _extensionBinding;\n // SECURITY (audit H4): announce the resolved binding to the parent so the\n // host bridge can gate dangerous helpers based on viewer role. Sent\n // BEFORE the user-authored content has a chance to run, so a malicious\n // extension body cannot suppress or rewrite the announcement. The parent\n // ignores subsequent announcements for the same iframe; see\n // ExtensionViewer.tsx / EmbeddedExtension.tsx.\n try {\n window.parent.postMessage(\n {\n type: 'agent-native-extension-binding',\n extensionId: _extensionId,\n binding: _extensionBinding,\n },\n '*',\n );\n } catch (_) {}\n // SECURITY: when the viewer is not the author of this extension, emit a\n // clear console warning. The bridge currently runs every helper with the\n // viewer's session — a malicious shared extension can call any action,\n // read any owned table row in scope, and resolve any user-scope secret.\n // A full consent step is tracked as TODO C1 in audit 05-tools-sandbox.md.\n if (_extensionBinding && !_extensionBinding.isAuthor) {\n try {\n console.warn(\n '[agent-native] Shared extension — running with viewer\\\\'s session. ' +\n 'Author: ' + (_extensionBinding.authorEmail || '<unknown>') + '. ' +\n 'Bridge calls (appAction, dbExec, extensionFetch) execute under ' +\n 'your account; they are gated by your permissions, not the ' +\n 'author\\\\'s. Do not run untrusted shared extensions.',\n );\n } catch (_) {}\n }\n\n var extensionData = {\n\t async list(collection, opts) {\n\t var limit = (opts && opts.limit) || 100;\n\t var scope = (opts && opts.scope) || 'user';\n\t var res = await hostRequest('/_agent-native/extensions/data/' + _extensionId + '/' + encodeURIComponent(collection) + '?limit=' + limit + '&scope=' + scope);\n\t if (!res.ok) throw new Error('Failed to list extension data');\n\t return res.body;\n\t },\n async get(collection, id, opts) {\n var scope = (opts && opts.scope) || 'user';\n var items = await this.list(collection, { scope: scope });\n return (items || []).find(function(item) { return item.id === id; }) || null;\n },\n async set(collection, id, data, opts) {\n\t var scope = (opts && opts.scope) || 'user';\n\t var res = await hostRequest('/_agent-native/extensions/data/' + _extensionId + '/' + encodeURIComponent(collection), {\n\t method: 'POST',\n\t headers: { 'Content-Type': 'application/json' },\n\t body: JSON.stringify({ id: id, data: data, scope: scope }),\n\t });\n\t if (!res.ok) throw new Error('Failed to save extension data');\n\t return res.body;\n\t },\n\t async remove(collection, id, opts) {\n\t var scope = (opts && opts.scope) || 'user';\n\t var res = await hostRequest('/_agent-native/extensions/data/' + _extensionId + '/' + encodeURIComponent(collection) + '/' + encodeURIComponent(id) + '?scope=' + scope, {\n\t method: 'DELETE',\n\t });\n\t if (!res.ok) throw new Error('Failed to delete extension data');\n\t return res.body;\n\t },\n\t };\n\n\t // Legacy aliases — extension bodies authored before the rename use\n\t // toolFetch, toolData, toolId. Keep these working forever.\n\t var toolFetch = extensionFetch;\n\t var toolData = extensionData;\n\t var _toolId = _extensionId;\n\t window.agentNative = Object.assign(window.agentNative || {}, {\n\t extensionId: _extensionId,\n\t extensionBinding: _extensionBinding,\n\t appAction: appAction,\n\t appFetch: appFetch,\n\t dbQuery: dbQuery,\n\t dbExec: dbExec,\n\t extensionFetch: extensionFetch,\n\t extensionData: extensionData,\n\t data: extensionData,\n\t sendToChat: sendToChat,\n\t chat: Object.assign({}, (window.agentNative && window.agentNative.chat) || {}, {\n\t send: sendToChat,\n\t }),\n\t ui: Object.assign({}, (window.agentNative && window.agentNative.ui) || {}, {\n\t output: outputToUi,\n\t }),\n\t });\n\t window.sendToAgentChat = sendToChat;\n\t </script>\n\t <style>\n\t #__extension-error-toast {\n\t display: none;\n\t position: fixed;\n\t bottom: 16px;\n\t right: 16px;\n\t max-width: 420px;\n\t background: hsl(var(--destructive));\n\t color: hsl(var(--destructive-foreground));\n\t border: 1px solid hsl(var(--destructive) / .6);\n\t border-radius: calc(var(--radius, .5rem) + 2px);\n\t padding: 12px 16px;\n\t font-size: 13px;\n\t line-height: 1.4;\n\t font-family: 'Inter', sans-serif;\n\t z-index: 9999;\n\t box-shadow: 0 4px 12px rgba(0,0,0,.15), 0 1px 3px rgba(0,0,0,.1);\n\t animation: __toast-in 0.2s ease-out;\n\t }\n\t @keyframes __toast-in {\n\t from { opacity: 0; transform: translateY(8px); }\n\t to { opacity: 1; transform: translateY(0); }\n\t }\n\t </style>\n\t <script>\n\t // Extension-point slot context: when an extension is rendered embedded\n\t // inside an ExtensionSlot, the host pushes a context object via\n\t // postMessage. Extensions read it synchronously via window.slotContext\n\t // or subscribe to changes via window.onSlotContext(fn). When rendered\n\t // full-page (no ?slot= param), slotContext stays null and extensions\n\t // branch on that.\n\t window.slotContext = null;\n\t var _slotContextSubscribers = [];\n\t window.onSlotContext = function(fn) {\n\t _slotContextSubscribers.push(fn);\n\t if (window.slotContext !== null) {\n\t try { fn(window.slotContext); } catch(_) {}\n\t }\n\t return function() {\n\t _slotContextSubscribers = _slotContextSubscribers.filter(function(f) { return f !== fn; });\n\t };\n\t };\n\t window.addEventListener('message', function(event) {\n\t if (event.source !== window.parent) return;\n\t var msg = event.data;\n\t if (!msg || msg.type !== 'agent-native-slot-context') return;\n\t window.slotContext = msg.context || {};\n\t _slotContextSubscribers.forEach(function(fn) {\n\t try { fn(window.slotContext); } catch(_) {}\n\t });\n\t });\n\n\t // Auto-resize iframe renders. Persisted extension slots include ?slot=;\n\t // transient inline chat UI uses srcdoc, so detect that by parent frame.\n\t // The host listens for agent-native-extension-resize and adjusts height.\n\t if (new URLSearchParams(location.search).get('slot') || window.parent !== window) {\n\t var _lastH = 0;\n\t var _reportHeight = function() {\n\t var h = Math.max(\n\t document.documentElement.scrollHeight,\n\t document.body ? document.body.scrollHeight : 0,\n\t );\n\t if (h !== _lastH) {\n\t _lastH = h;\n\t window.parent.postMessage({ type: 'agent-native-extension-resize', height: h }, '*');\n\t }\n\t };\n\t if (typeof ResizeObserver !== 'undefined') {\n\t var _ro = new ResizeObserver(_reportHeight);\n\t document.addEventListener('DOMContentLoaded', function() {\n\t _ro.observe(document.documentElement);\n\t if (document.body) _ro.observe(document.body);\n\t });\n\t }\n\t // Initial reports — Alpine takes a tick to render after DOMContentLoaded.\n\t setTimeout(_reportHeight, 50);\n\t setTimeout(_reportHeight, 250);\n\t }\n\n\t window.addEventListener('message', function(event) {\n\t if (event.source !== window.parent) return;\n\t var msg = event.data;\n\t if (!msg || msg.type !== 'agent-native-theme-update') return;\n\t var root = document.documentElement;\n\t if (msg.isDark !== undefined) {\n\t if (msg.isDark) root.classList.add('dark');\n\t else root.classList.remove('dark');\n\t }\n\t var vars = msg.vars || {};\n\t for (var key in vars) {\n\t if (vars.hasOwnProperty(key)) {\n\t root.style.setProperty(key, vars[key]);\n\t }\n\t }\n\t });\n\n\t document.addEventListener('keydown', function(e) {\n\t if ((e.metaKey || e.ctrlKey) && !e.altKey) {\n\t var key = (e.key || '').toLowerCase();\n\t if (key === 'c' || key === 'v' || key === 'x' || key === 'a' || key === 'z' || key === 'y') return;\n\t e.preventDefault();\n\t e.stopPropagation();\n\t window.parent.postMessage({\n\t type: 'agent-native-extension-keydown',\n\t key: e.key, code: e.code,\n\t metaKey: e.metaKey, ctrlKey: e.ctrlKey,\n\t shiftKey: e.shiftKey, altKey: e.altKey,\n\t }, '*');\n\t return;\n\t }\n\t if (e.key === 'Escape') {\n\t window.parent.postMessage({\n\t type: 'agent-native-extension-keydown',\n\t key: e.key, code: e.code,\n\t metaKey: false, ctrlKey: false,\n\t shiftKey: false, altKey: false,\n\t }, '*');\n\t }\n\t });\n\n\t document.addEventListener('DOMContentLoaded', function() {\n\t _renderErrorToast();\n\t var fixBtn = document.getElementById('__extension-error-fix');\n\t if (fixBtn) {\n\t fixBtn.addEventListener('click', function() {\n\t window.parent.postMessage({\n\t type: 'agent-native-extension-error-fix',\n\t errors: _extensionErrors,\n\t errorDetails: _extensionErrorDetails,\n\t consoleLogs: _consoleLogs.slice(-30),\n\t networkLogs: _networkLogs.slice(-15)\n\t }, '*');\n\t document.getElementById('__extension-error-toast').style.display = 'none';\n\t });\n\t }\n\t var dismissBtn = document.getElementById('__extension-error-dismiss');\n\t if (dismissBtn) {\n\t dismissBtn.addEventListener('click', function() {\n\t document.getElementById('__extension-error-toast').style.display = 'none';\n\t });\n\t }\n\t });\n\t </script>\n\t</head>\n\t<body${extensionId ? ` data-extension-id=\"${extensionIdAttr}\" data-tool-id=\"${extensionIdAttr}\"` : \"\"} class=\"text-foreground\">\n\t${content}\n\t<div id=\"__extension-error-toast\">\n\t <div style=\"display:flex;align-items:flex-start;gap:8px;\">\n\t <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"flex-shrink:0;margin-top:1px;\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\"/><line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\"/></svg>\n\t <span id=\"__extension-error-msg\" style=\"flex:1;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;\"></span>\n\t <button id=\"__extension-error-fix\" style=\"cursor:pointer;border:none;background:rgba(255,255,255,.9);color:hsl(0 84.2% 40%);font-size:12px;font-weight:500;padding:4px 12px;border-radius:4px;flex-shrink:0;\">Fix</button>\n\t <button id=\"__extension-error-dismiss\" style=\"cursor:pointer;border:none;background:transparent;color:inherit;font-size:16px;padding:2px 6px;opacity:0.7;flex-shrink:0;\">×</button>\n\t </div>\n\t</div>\n\t</body>\n\t</html>`;\n}\n\nfunction escapeHtmlAttribute(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n"]}
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical set of semantic theme token names shared by the app and extension
|
|
3
|
+
* iframes. This is the single source of truth: `getThemeVars` bakes default
|
|
4
|
+
* values for these into every iframe, and the host reads the *actual* computed
|
|
5
|
+
* values for the same names to sync its live palette into embedded extensions
|
|
6
|
+
* (see EmbeddedExtension). Custom properties can't be enumerated portably from
|
|
7
|
+
* a computed style, so the names must be listed explicitly.
|
|
8
|
+
*/
|
|
9
|
+
export declare const THEME_VAR_NAMES: readonly ["--background", "--foreground", "--card", "--card-foreground", "--popover", "--popover-foreground", "--primary", "--primary-foreground", "--secondary", "--secondary-foreground", "--muted", "--muted-foreground", "--accent", "--accent-foreground", "--destructive", "--destructive-foreground", "--border", "--input", "--ring", "--radius", "--sidebar-background", "--sidebar-foreground", "--sidebar-primary", "--sidebar-primary-foreground", "--sidebar-accent", "--sidebar-accent-foreground", "--sidebar-border", "--sidebar-ring"];
|
|
1
10
|
/**
|
|
2
11
|
* CSS variables baked into every extension iframe. Both light and dark are
|
|
3
12
|
* always emitted — the `.dark` class on the iframe's `<html>` toggles between
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/extensions/theme.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAmEtD"}
|
|
1
|
+
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/extensions/theme.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,YAC1B,cAAc,EACd,cAAc,EACd,QAAQ,EACR,mBAAmB,EACnB,WAAW,EACX,sBAAsB,EACtB,WAAW,EACX,sBAAsB,EACtB,aAAa,EACb,wBAAwB,EACxB,SAAS,EACT,oBAAoB,EACpB,UAAU,EACV,qBAAqB,EACrB,eAAe,EACf,0BAA0B,EAC1B,UAAU,EACV,SAAS,EACT,QAAQ,EACR,UAAU,EACV,sBAAsB,EACtB,sBAAsB,EACtB,mBAAmB,EACnB,8BAA8B,EAC9B,kBAAkB,EAClB,6BAA6B,EAC7B,kBAAkB,EAClB,gBAAgB,CACR,CAAC;AAEX;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAmEtD"}
|
package/dist/extensions/theme.js
CHANGED
|
@@ -1,3 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical set of semantic theme token names shared by the app and extension
|
|
3
|
+
* iframes. This is the single source of truth: `getThemeVars` bakes default
|
|
4
|
+
* values for these into every iframe, and the host reads the *actual* computed
|
|
5
|
+
* values for the same names to sync its live palette into embedded extensions
|
|
6
|
+
* (see EmbeddedExtension). Custom properties can't be enumerated portably from
|
|
7
|
+
* a computed style, so the names must be listed explicitly.
|
|
8
|
+
*/
|
|
9
|
+
export const THEME_VAR_NAMES = [
|
|
10
|
+
"--background",
|
|
11
|
+
"--foreground",
|
|
12
|
+
"--card",
|
|
13
|
+
"--card-foreground",
|
|
14
|
+
"--popover",
|
|
15
|
+
"--popover-foreground",
|
|
16
|
+
"--primary",
|
|
17
|
+
"--primary-foreground",
|
|
18
|
+
"--secondary",
|
|
19
|
+
"--secondary-foreground",
|
|
20
|
+
"--muted",
|
|
21
|
+
"--muted-foreground",
|
|
22
|
+
"--accent",
|
|
23
|
+
"--accent-foreground",
|
|
24
|
+
"--destructive",
|
|
25
|
+
"--destructive-foreground",
|
|
26
|
+
"--border",
|
|
27
|
+
"--input",
|
|
28
|
+
"--ring",
|
|
29
|
+
"--radius",
|
|
30
|
+
"--sidebar-background",
|
|
31
|
+
"--sidebar-foreground",
|
|
32
|
+
"--sidebar-primary",
|
|
33
|
+
"--sidebar-primary-foreground",
|
|
34
|
+
"--sidebar-accent",
|
|
35
|
+
"--sidebar-accent-foreground",
|
|
36
|
+
"--sidebar-border",
|
|
37
|
+
"--sidebar-ring",
|
|
38
|
+
];
|
|
1
39
|
/**
|
|
2
40
|
* CSS variables baked into every extension iframe. Both light and dark are
|
|
3
41
|
* always emitted — the `.dark` class on the iframe's `<html>` toggles between
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.js","sourceRoot":"","sources":["../../src/extensions/theme.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,OAAiB;IAC5C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiEP,CAAC;AACH,CAAC","sourcesContent":["/**\n * CSS variables baked into every extension iframe. Both light and dark are\n * always emitted — the `.dark` class on the iframe's `<html>` toggles between\n * them. This means a parent theme toggle becomes a single class toggle inside\n * the iframe (cheap, atomic, no full reload), and there is no race where the\n * iframe is briefly half-themed while postMessage values arrive.\n */\nexport function getThemeVars(_isDark?: boolean): string {\n return `\n:root {\n --background: 0 0% 100%;\n --foreground: 240 10% 3.9%;\n --card: 0 0% 100%;\n --card-foreground: 240 10% 3.9%;\n --popover: 0 0% 100%;\n --popover-foreground: 240 10% 3.9%;\n --primary: 240 5.9% 10%;\n --primary-foreground: 0 0% 98%;\n --secondary: 240 4.8% 95.9%;\n --secondary-foreground: 240 5.9% 10%;\n --muted: 240 4.8% 95.9%;\n --muted-foreground: 240 3.8% 46.1%;\n --accent: 240 4.8% 95.9%;\n --accent-foreground: 240 5.9% 10%;\n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 0 0% 98%;\n --border: 240 5.9% 90%;\n --input: 240 5.9% 90%;\n --ring: 240 5.9% 10%;\n --radius: 0.5rem;\n --sidebar-background: 0 0% 98%;\n --sidebar-foreground: 240 5.3% 26.1%;\n --sidebar-primary: 240 5.9% 10%;\n --sidebar-primary-foreground: 0 0% 98%;\n --sidebar-accent: 240 4.8% 95.9%;\n --sidebar-accent-foreground: 240 5.9% 10%;\n --sidebar-border: 220 13% 91%;\n --sidebar-ring: 240 5.9% 10%;\n color-scheme: light;\n}\n.dark {\n --background: 240 10% 3.9%;\n --foreground: 0 0% 98%;\n --card: 240 10% 3.9%;\n --card-foreground: 0 0% 98%;\n --popover: 240 10% 3.9%;\n --popover-foreground: 0 0% 98%;\n --primary: 0 0% 98%;\n --primary-foreground: 240 5.9% 10%;\n --secondary: 240 3.7% 15.9%;\n --secondary-foreground: 0 0% 98%;\n --muted: 240 3.7% 15.9%;\n --muted-foreground: 240 5% 64.9%;\n --accent: 240 3.7% 15.9%;\n --accent-foreground: 0 0% 98%;\n --destructive: 0 62.8% 30.6%;\n --destructive-foreground: 0 0% 98%;\n --border: 240 3.7% 15.9%;\n --input: 240 3.7% 15.9%;\n --ring: 240 4.9% 83.9%;\n --sidebar-background: 240 5.9% 10%;\n --sidebar-foreground: 240 4.8% 95.9%;\n --sidebar-primary: 224.3 76.3% 48%;\n --sidebar-primary-foreground: 0 0% 100%;\n --sidebar-accent: 240 3.7% 15.9%;\n --sidebar-accent-foreground: 240 4.8% 95.9%;\n --sidebar-border: 240 3.7% 15.9%;\n --sidebar-ring: 240 4.9% 83.9%;\n color-scheme: dark;\n}\nbody {\n background-color: hsl(var(--background));\n color: hsl(var(--foreground));\n}`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"theme.js","sourceRoot":"","sources":["../../src/extensions/theme.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,cAAc;IACd,cAAc;IACd,QAAQ;IACR,mBAAmB;IACnB,WAAW;IACX,sBAAsB;IACtB,WAAW;IACX,sBAAsB;IACtB,aAAa;IACb,wBAAwB;IACxB,SAAS;IACT,oBAAoB;IACpB,UAAU;IACV,qBAAqB;IACrB,eAAe;IACf,0BAA0B;IAC1B,UAAU;IACV,SAAS;IACT,QAAQ;IACR,UAAU;IACV,sBAAsB;IACtB,sBAAsB;IACtB,mBAAmB;IACnB,8BAA8B;IAC9B,kBAAkB;IAClB,6BAA6B;IAC7B,kBAAkB;IAClB,gBAAgB;CACR,CAAC;AAEX;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,OAAiB;IAC5C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiEP,CAAC;AACH,CAAC","sourcesContent":["/**\n * Canonical set of semantic theme token names shared by the app and extension\n * iframes. This is the single source of truth: `getThemeVars` bakes default\n * values for these into every iframe, and the host reads the *actual* computed\n * values for the same names to sync its live palette into embedded extensions\n * (see EmbeddedExtension). Custom properties can't be enumerated portably from\n * a computed style, so the names must be listed explicitly.\n */\nexport const THEME_VAR_NAMES = [\n \"--background\",\n \"--foreground\",\n \"--card\",\n \"--card-foreground\",\n \"--popover\",\n \"--popover-foreground\",\n \"--primary\",\n \"--primary-foreground\",\n \"--secondary\",\n \"--secondary-foreground\",\n \"--muted\",\n \"--muted-foreground\",\n \"--accent\",\n \"--accent-foreground\",\n \"--destructive\",\n \"--destructive-foreground\",\n \"--border\",\n \"--input\",\n \"--ring\",\n \"--radius\",\n \"--sidebar-background\",\n \"--sidebar-foreground\",\n \"--sidebar-primary\",\n \"--sidebar-primary-foreground\",\n \"--sidebar-accent\",\n \"--sidebar-accent-foreground\",\n \"--sidebar-border\",\n \"--sidebar-ring\",\n] as const;\n\n/**\n * CSS variables baked into every extension iframe. Both light and dark are\n * always emitted — the `.dark` class on the iframe's `<html>` toggles between\n * them. This means a parent theme toggle becomes a single class toggle inside\n * the iframe (cheap, atomic, no full reload), and there is no race where the\n * iframe is briefly half-themed while postMessage values arrive.\n */\nexport function getThemeVars(_isDark?: boolean): string {\n return `\n:root {\n --background: 0 0% 100%;\n --foreground: 240 10% 3.9%;\n --card: 0 0% 100%;\n --card-foreground: 240 10% 3.9%;\n --popover: 0 0% 100%;\n --popover-foreground: 240 10% 3.9%;\n --primary: 240 5.9% 10%;\n --primary-foreground: 0 0% 98%;\n --secondary: 240 4.8% 95.9%;\n --secondary-foreground: 240 5.9% 10%;\n --muted: 240 4.8% 95.9%;\n --muted-foreground: 240 3.8% 46.1%;\n --accent: 240 4.8% 95.9%;\n --accent-foreground: 240 5.9% 10%;\n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 0 0% 98%;\n --border: 240 5.9% 90%;\n --input: 240 5.9% 90%;\n --ring: 240 5.9% 10%;\n --radius: 0.5rem;\n --sidebar-background: 0 0% 98%;\n --sidebar-foreground: 240 5.3% 26.1%;\n --sidebar-primary: 240 5.9% 10%;\n --sidebar-primary-foreground: 0 0% 98%;\n --sidebar-accent: 240 4.8% 95.9%;\n --sidebar-accent-foreground: 240 5.9% 10%;\n --sidebar-border: 220 13% 91%;\n --sidebar-ring: 240 5.9% 10%;\n color-scheme: light;\n}\n.dark {\n --background: 240 10% 3.9%;\n --foreground: 0 0% 98%;\n --card: 240 10% 3.9%;\n --card-foreground: 0 0% 98%;\n --popover: 240 10% 3.9%;\n --popover-foreground: 0 0% 98%;\n --primary: 0 0% 98%;\n --primary-foreground: 240 5.9% 10%;\n --secondary: 240 3.7% 15.9%;\n --secondary-foreground: 0 0% 98%;\n --muted: 240 3.7% 15.9%;\n --muted-foreground: 240 5% 64.9%;\n --accent: 240 3.7% 15.9%;\n --accent-foreground: 0 0% 98%;\n --destructive: 0 62.8% 30.6%;\n --destructive-foreground: 0 0% 98%;\n --border: 240 3.7% 15.9%;\n --input: 240 3.7% 15.9%;\n --ring: 240 4.9% 83.9%;\n --sidebar-background: 240 5.9% 10%;\n --sidebar-foreground: 240 4.8% 95.9%;\n --sidebar-primary: 224.3 76.3% 48%;\n --sidebar-primary-foreground: 0 0% 100%;\n --sidebar-accent: 240 3.7% 15.9%;\n --sidebar-accent-foreground: 240 4.8% 95.9%;\n --sidebar-border: 240 3.7% 15.9%;\n --sidebar-ring: 240 4.9% 83.9%;\n color-scheme: dark;\n}\nbody {\n background-color: hsl(var(--background));\n color: hsl(var(--foreground));\n}`;\n}\n"]}
|
|
@@ -3,18 +3,18 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
3
3
|
url?: string;
|
|
4
4
|
filename?: string;
|
|
5
5
|
}, {
|
|
6
|
+
id?: undefined;
|
|
6
7
|
error: string;
|
|
7
8
|
configured?: undefined;
|
|
8
9
|
connectPath?: undefined;
|
|
9
10
|
url?: undefined;
|
|
10
|
-
id?: undefined;
|
|
11
11
|
provider?: undefined;
|
|
12
12
|
} | {
|
|
13
|
+
id?: undefined;
|
|
13
14
|
error: string;
|
|
14
15
|
configured: boolean;
|
|
15
16
|
connectPath: string;
|
|
16
17
|
url?: undefined;
|
|
17
|
-
id?: undefined;
|
|
18
18
|
provider?: undefined;
|
|
19
19
|
} | {
|
|
20
20
|
error?: undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../src/file-upload/builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAKnB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../src/file-upload/builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAKnB,MAAM,YAAY,CAAC;AAwMpB;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB,EAAE,kBAgMvC,CAAC"}
|
|
@@ -13,6 +13,9 @@ function makeBody(bytes, mimeType) {
|
|
|
13
13
|
? new Blob([bytes], { type: mimeType })
|
|
14
14
|
: bytes;
|
|
15
15
|
}
|
|
16
|
+
function shouldUseSignedUrlUpload(bytes, mimeType) {
|
|
17
|
+
return (bytes.byteLength > LARGE_FILE_THRESHOLD_BYTES || /^video\//i.test(mimeType));
|
|
18
|
+
}
|
|
16
19
|
function fetchWithTimeout(url, init) {
|
|
17
20
|
const controller = new AbortController();
|
|
18
21
|
const timer = setTimeout(() => controller.abort(), UPLOAD_TIMEOUT_MS);
|
|
@@ -44,7 +47,7 @@ async function uploadLargeFileViaSignedUrl(input, privateKey, bareMimeType, byte
|
|
|
44
47
|
console.log(`[builder-upload] step 2 ok [${assetId}]: GCS ${step2Res.status} etag=${step2Res.headers.get("etag") ?? "none"}`);
|
|
45
48
|
// Step 3 — register the asset and get the CDN URL.
|
|
46
49
|
console.log(`[builder-upload] step 3: registering asset - ${assetId}, ${input.filename}`);
|
|
47
|
-
const { url, id } = await completeBuilderUpload(privateKey, assetId, input.filename);
|
|
50
|
+
const { url, id } = await completeBuilderUpload(privateKey, assetId, input.filename, { skipCompressionWait: input.skipCompressionWait });
|
|
48
51
|
console.log(`[builder-upload] done [${assetId}]: ${url}`);
|
|
49
52
|
return { url, id, provider: "builder" };
|
|
50
53
|
}
|
|
@@ -75,9 +78,13 @@ async function requestBuilderSignedUrl(privateKey, filename, mimeType, size, res
|
|
|
75
78
|
requiredHeaders: json.requiredHeaders,
|
|
76
79
|
};
|
|
77
80
|
}
|
|
78
|
-
async function completeBuilderUpload(privateKey, assetId, filename) {
|
|
81
|
+
async function completeBuilderUpload(privateKey, assetId, filename, options) {
|
|
79
82
|
const host = builderUploadHost();
|
|
80
|
-
const
|
|
83
|
+
const url = new URL("/api/v1/upload/complete", host);
|
|
84
|
+
if (options?.skipCompressionWait) {
|
|
85
|
+
url.searchParams.set("skipCompressionWait", "true");
|
|
86
|
+
}
|
|
87
|
+
const res = await fetchWithTimeout(url.toString(), {
|
|
81
88
|
method: "POST",
|
|
82
89
|
headers: {
|
|
83
90
|
Authorization: `Bearer ${privateKey}`,
|
|
@@ -132,7 +139,8 @@ export const builderFileUploadProvider = {
|
|
|
132
139
|
id: "builder",
|
|
133
140
|
name: "Builder.io",
|
|
134
141
|
isConfigured: () => !!process.env.BUILDER_PRIVATE_KEY,
|
|
135
|
-
upload: async (
|
|
142
|
+
upload: async (input) => {
|
|
143
|
+
const { data, filename, mimeType } = input;
|
|
136
144
|
const { resolveBuilderPrivateKey } = await import("../server/credential-provider.js");
|
|
137
145
|
const privateKey = await resolveBuilderPrivateKey();
|
|
138
146
|
if (!privateKey) {
|
|
@@ -149,13 +157,16 @@ export const builderFileUploadProvider = {
|
|
|
149
157
|
.trim();
|
|
150
158
|
const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
151
159
|
const mb = (bytes.byteLength / (1024 * 1024)).toFixed(1);
|
|
152
|
-
if (bytes
|
|
153
|
-
return uploadLargeFileViaSignedUrl(
|
|
160
|
+
if (shouldUseSignedUrlUpload(bytes, bareMimeType)) {
|
|
161
|
+
return uploadLargeFileViaSignedUrl(input, privateKey, bareMimeType, bytes);
|
|
154
162
|
}
|
|
155
163
|
console.log(`[builder-upload] small-file path: ${filename ?? "upload"} ${mb}MB ${bareMimeType}`);
|
|
156
164
|
const url = new URL("/api/v1/upload", builderUploadHost());
|
|
157
165
|
if (filename)
|
|
158
166
|
url.searchParams.set("name", filename);
|
|
167
|
+
if (input.skipCompressionWait) {
|
|
168
|
+
url.searchParams.set("skipCompressionWait", "true");
|
|
169
|
+
}
|
|
159
170
|
const response = await uploadSmallFile(url, {
|
|
160
171
|
method: "POST",
|
|
161
172
|
headers: {
|
|
@@ -251,14 +262,17 @@ export const builderFileUploadProvider = {
|
|
|
251
262
|
? lastError
|
|
252
263
|
: new Error("GCS PUT failed after retries");
|
|
253
264
|
},
|
|
254
|
-
async completeSession(session, filename) {
|
|
265
|
+
async completeSession(session, filename, options) {
|
|
255
266
|
const { resolveBuilderPrivateKey } = await import("../server/credential-provider.js");
|
|
256
267
|
const privateKey = await resolveBuilderPrivateKey();
|
|
257
268
|
if (!privateKey)
|
|
258
269
|
throw new Error("BUILDER_PRIVATE_KEY is not set");
|
|
259
270
|
const assetId = session.meta.assetId;
|
|
260
271
|
console.log(`[builder-resumable] completing upload: assetId=${assetId}`);
|
|
261
|
-
const { url } = await completeBuilderUpload(privateKey, assetId, filename
|
|
272
|
+
const { url } = await completeBuilderUpload(privateKey, assetId, filename, {
|
|
273
|
+
skipCompressionWait: options?.skipCompressionWait ||
|
|
274
|
+
session.meta.skipCompressionWait === true,
|
|
275
|
+
});
|
|
262
276
|
console.log(`[builder-resumable] upload complete: ${url}`);
|
|
263
277
|
return url;
|
|
264
278
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builder.js","sourceRoot":"","sources":["../../src/file-upload/builder.ts"],"names":[],"mappings":"AAQA,MAAM,wBAAwB,GAAG,oBAAoB,CAAC;AAEtD,yEAAyE;AACzE,MAAM,0BAA0B,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AACpD,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAClC,MAAM,0BAA0B,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AAE/C,SAAS,iBAAiB;IACxB,OAAO,CACL,OAAO,CAAC,GAAG,CAAC,gBAAgB;QAC5B,OAAO,CAAC,GAAG,CAAC,uBAAuB;QACnC,wBAAwB,CACzB,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,KAAiB,EAAE,QAAgB;IACnD,OAAO,OAAO,IAAI,KAAK,WAAW;QAChC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,KAA4B,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;QAC9D,CAAC,CAAE,KAA6B,CAAC;AACrC,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAW,EAAE,IAAiB;IACtD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,iBAAiB,CAAC,CAAC;IACtE,OAAO,KAAK,CAAC,GAAG,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CACrE,YAAY,CAAC,KAAK,CAAC,CACpB,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,GAAa,EAAE,KAAa;IAClD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,GAAG,CAAC,MAAM,MAAM,IAAI,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;IACzE,CAAC;AACH,CAAC;AAED,KAAK,UAAU,2BAA2B,CACxC,KAAsB,EACtB,UAAkB,EAClB,YAAoB,EACpB,KAAiB;IAEjB,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,IAAI,QAAQ,CAAC;IACxC,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAEzD,OAAO,CAAC,GAAG,CACT,qCAAqC,IAAI,IAAI,EAAE,MAAM,YAAY,EAAE,CACpE,CAAC;IAEF,iCAAiC;IACjC,OAAO,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC;IAC9D,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,EAAE,GAAG,MAAM,uBAAuB,CAC3E,UAAU,EACV,IAAI,EACJ,YAAY,EACZ,KAAK,CAAC,UAAU,CACjB,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,uCAAuC,OAAO,EAAE,CAAC,CAAC;IAE9D,6EAA6E;IAC7E,gFAAgF;IAChF,OAAO,CAAC,GAAG,CAAC,4BAA4B,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;IACxE,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,SAAS,EAAE;QACjD,MAAM,EAAE,KAAK;QACb,OAAO,EAAE,eAAe;QACxB,IAAI,EAAE,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;KACpC,CAAC,CAAC;IACH,MAAM,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC,CAAC;IAC9C,OAAO,CAAC,GAAG,CACT,+BAA+B,OAAO,UAAU,QAAQ,CAAC,MAAM,SAAS,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,MAAM,EAAE,CACjH,CAAC;IAEF,mDAAmD;IACnD,OAAO,CAAC,GAAG,CACT,gDAAgD,OAAO,KAAK,KAAK,CAAC,QAAQ,EAAE,CAC7E,CAAC;IACF,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,MAAM,qBAAqB,CAC7C,UAAU,EACV,OAAO,EACP,KAAK,CAAC,QAAQ,CACf,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,0BAA0B,OAAO,MAAM,GAAG,EAAE,CAAC,CAAC;IAC1D,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AAC1C,CAAC;AAED,KAAK,UAAU,uBAAuB,CACpC,UAAkB,EAClB,QAAgB,EAChB,QAAgB,EAChB,IAAY,EACZ,SAAS,GAAG,KAAK;IAMjB,MAAM,IAAI,GAAG,iBAAiB,EAAE,CAAC;IACjC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,2BAA2B,EAAE,IAAI,CAAC,CAAC;IACvD,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE;QACjD,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,UAAU,EAAE;YACrC,cAAc,EAAE,kBAAkB;SACnC;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,QAAQ,EAAE,QAAQ;YAClB,WAAW,EAAE,QAAQ;YACrB,IAAI;YACJ,SAAS;SACV,CAAC;KACH,CAAC,CAAC;IACH,MAAM,QAAQ,CAAC,GAAG,EAAE,sCAAsC,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAI7B,CAAC;IACF,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;QAC9D,MAAM,IAAI,KAAK,CACb,2DAA2D,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAC/F,CAAC;IACJ,CAAC;IACD,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,eAAe,EAAE,IAAI,CAAC,eAAe;KACtC,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,qBAAqB,CAClC,UAAkB,EAClB,OAAe,EACf,QAA4B;IAE5B,MAAM,IAAI,GAAG,iBAAiB,EAAE,CAAC;IACjC,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAChC,IAAI,GAAG,CAAC,yBAAyB,EAAE,IAAI,CAAC,CAAC,QAAQ,EAAE,EACnD;QACE,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,UAAU,EAAE;YACrC,cAAc,EAAE,kBAAkB;SACnC;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;KAClD,CACF,CAAC;IACF,MAAM,QAAQ,CAAC,GAAG,EAAE,mCAAmC,CAAC,CAAC;IACzD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAkC,CAAC;IACjE,IAAI,CAAC,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAC7E,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;AACxC,CAAC;AAED,qEAAqE;AACrE,sEAAsE;AACtE,6DAA6D;AAC7D,KAAK,UAAU,eAAe,CAAC,GAAQ,EAAE,IAAiB;IACxD,IAAI,QAAQ,GAAoB,IAAI,CAAC;IACrC,IAAI,aAAa,GAAG,EAAE,CAAC;IAEvB,KACE,IAAI,OAAO,GAAG,CAAC,EACf,OAAO,IAAI,0BAA0B,CAAC,MAAM,EAC5C,OAAO,EAAE,EACT,CAAC;QACD,MAAM,UAAU,GAAG,0BAA0B,CAAC,OAAO,CAAC,CAAC,CAAC,4BAA4B;QACpF,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,IAAI,CAAC,CAAC;QAC1D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,UAAU;gBAAE,MAAM,GAAG,CAAC;YAC3B,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;YACpD,SAAS;QACX,CAAC;QACD,IAAI,QAAQ,CAAC,EAAE;YAAE,OAAO,QAAQ,CAAC;QACjC,aAAa,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QACtD,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,CAAC;QACtE,IAAI,CAAC,WAAW,IAAI,CAAC,UAAU;YAAE,MAAM;QACvC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;IACtD,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,EAAE,MAAM,IAAI,CAAC,CAAC;IACrC,MAAM,UAAU,GAAG,QAAQ,EAAE,UAAU,IAAI,aAAa,CAAC;IACzD,MAAM,IAAI,KAAK,CACb,6BAA6B,MAAM,MAAM,aAAa,IAAI,UAAU,EAAE,CACvE,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAuB;IAC3D,EAAE,EAAE,SAAS;IACb,IAAI,EAAE,YAAY;IAClB,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB;IACrD,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAmB,EAAE,EAAE;QAC9D,MAAM,EAAE,wBAAwB,EAAE,GAChC,MAAM,MAAM,CAAC,kCAAkC,CAAC,CAAC;QACnD,MAAM,UAAU,GAAG,MAAM,wBAAwB,EAAE,CAAC;QACpD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;QACpD,CAAC;QAED,iEAAiE;QACjE,qEAAqE;QACrE,qEAAqE;QACrE,sEAAsE;QACtE,iEAAiE;QACjE,oBAAoB;QACpB,MAAM,YAAY,GAAG,CAAC,QAAQ,IAAI,0BAA0B,CAAC;aAC1D,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;aACb,IAAI,EAAE,CAAC;QAEV,MAAM,KAAK,GACT,IAAI,YAAY,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,IAAW,CAAC,CAAC;QAClE,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAEzD,IAAI,KAAK,CAAC,UAAU,GAAG,0BAA0B,EAAE,CAAC;YAClD,OAAO,2BAA2B,CAChC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAC5B,UAAU,EACV,YAAY,EACZ,KAAK,CACN,CAAC;QACJ,CAAC;QAED,OAAO,CAAC,GAAG,CACT,qCAAqC,QAAQ,IAAI,QAAQ,IAAI,EAAE,MAAM,YAAY,EAAE,CACpF,CAAC;QAEF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,gBAAgB,EAAE,iBAAiB,EAAE,CAAC,CAAC;QAC3D,IAAI,QAAQ;YAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAErD,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAC,GAAG,EAAE;YAC1C,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,UAAU,EAAE;gBACrC,cAAc,EAAE,YAAY;aAC7B;YACD,IAAI,EAAE,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;SACpC,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAGpD,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;QAEpE,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAClD,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAC7D,CAAC;IAED,SAAS,EAAE;QACT,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ;YAC7C,MAAM,EAAE,wBAAwB,EAAE,GAChC,MAAM,MAAM,CAAC,kCAAkC,CAAC,CAAC;YACnD,MAAM,UAAU,GAAG,MAAM,wBAAwB,EAAE,CAAC;YACpD,IAAI,CAAC,UAAU;gBAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;YAEnE,OAAO,CAAC,GAAG,CACT,yCAAyC,QAAQ,IAAI,QAAQ,IAAI,QAAQ,QAAQ,CAClF,CAAC;YACF,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,EAAE,GAC3C,MAAM,uBAAuB,CAC3B,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,IAAI,CACL,CAAC;YACJ,OAAO,CAAC,GAAG,CAAC,kDAAkD,OAAO,EAAE,CAAC,CAAC;YAEzE,MAAM,WAAW,GAA2B;gBAC1C,cAAc,EAAE,QAAQ;gBACxB,kBAAkB,EAAE,OAAO;aAC5B,CAAC;YACF,MAAM,kBAAkB,GACtB,eAAe,EAAE,CAAC,6BAA6B,CAAC,CAAC;YACnD,IAAI,kBAAkB;gBACpB,WAAW,CAAC,6BAA6B,CAAC,GAAG,kBAAkB,CAAC;YAElE,OAAO,CAAC,GAAG,CAAC,4DAA4D,CAAC,CAAC;YAC1E,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,SAAS,EAAE;gBAChD,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,WAAW;gBACpB,IAAI,EAAE,IAAI,UAAU,CAAC,CAAC,CAAC;aACxB,CAAC,CAAC;YACH,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;gBAChB,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;gBAClD,MAAM,IAAI,KAAK,CACb,4CAA4C,OAAO,CAAC,MAAM,MAAM,IAAI,EAAE,CACvE,CAAC;YACJ,CAAC;YACD,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACnD,IAAI,CAAC,UAAU;gBACb,MAAM,IAAI,KAAK,CACb,gEAAgE,CACjE,CAAC;YAEJ,OAAO,CAAC,GAAG,CAAC,8CAA8C,OAAO,EAAE,CAAC,CAAC;YACrE,OAAO;gBACL,SAAS,EAAE,UAAU;gBACrB,IAAI,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE;aACL,CAAC;QACrC,CAAC;QAED,KAAK,CAAC,UAAU,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO;YACpD,MAAM,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;YACrC,MAAM,YAAY,GAAG,CAAC,CAAC;YACvB,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;YAC1D,MAAM,OAAO,GAAG,CAAC,OAAe,EAAE,EAAE,CAClC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC;YAE3C,IAAI,SAAS,GAAY,IAAI,CAAC;YAC9B,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,YAAY,EAAE,OAAO,EAAE,EAAE,CAAC;gBACzD,IAAI,CAAC;oBACH,MAAM,OAAO,GAA2B;wBACtC,eAAe,EAAE,YAAY;qBAC9B,CAAC;oBACF,IAAI,OAAO,EAAE,QAAQ;wBAAE,OAAO,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;oBAClE,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,UAAU,EAAE;wBAClC,MAAM,EAAE,KAAK;wBACb,OAAO;wBACP,IAAI,EAAE,KAA4B;qBACnC,CAAC,CAAC;oBACH,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,EAAE;wBAC9B,OAAO;4BACL,EAAE,EAAE,IAAI;4BACR,MAAM,EAAE,GAAG,CAAC,MAAM;yBACY,CAAC;oBACnC,IAAI,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,OAAO,GAAG,YAAY,EAAE,CAAC;wBACxD,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;wBACjC,OAAO,CAAC,IAAI,CACV,iCAAiC,GAAG,CAAC,MAAM,eAAe,OAAO,YAAY,CAC9E,CAAC;wBACF,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;wBAC1D,SAAS;oBACX,CAAC;oBACD,OAAO;wBACL,EAAE,EAAE,KAAK;wBACT,MAAM,EAAE,GAAG,CAAC,MAAM;qBACY,CAAC;gBACnC,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,SAAS,GAAG,GAAG,CAAC;oBAChB,IAAI,OAAO,IAAI,YAAY;wBAAE,MAAM;oBACnC,OAAO,CAAC,IAAI,CACV,gDAAgD,OAAO,GAAG,EAC1D,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CACjD,CAAC;oBACF,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBAC5D,CAAC;YACH,CAAC;YACD,MAAM,SAAS,YAAY,KAAK;gBAC9B,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAChD,CAAC;QAED,KAAK,CAAC,eAAe,CAAC,OAAO,EAAE,QAAQ;YACrC,MAAM,EAAE,wBAAwB,EAAE,GAChC,MAAM,MAAM,CAAC,kCAAkC,CAAC,CAAC;YACnD,MAAM,UAAU,GAAG,MAAM,wBAAwB,EAAE,CAAC;YACpD,IAAI,CAAC,UAAU;gBAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;YAEnE,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,OAAiB,CAAC;YAC/C,OAAO,CAAC,GAAG,CAAC,kDAAkD,OAAO,EAAE,CAAC,CAAC;YACzE,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,qBAAqB,CACzC,UAAU,EACV,OAAO,EACP,QAAQ,CACT,CAAC;YACF,OAAO,CAAC,GAAG,CAAC,wCAAwC,GAAG,EAAE,CAAC,CAAC;YAC3D,OAAO,GAAG,CAAC;QACb,CAAC;KACF;CACF,CAAC","sourcesContent":["import type {\n FileUploadProvider,\n FileUploadInput,\n FileUploadResult,\n ResumableUploadSession,\n ResumableChunkResult,\n} from \"./types.js\";\n\nconst DEFAULT_BUILDER_APP_HOST = \"https://builder.io\";\n\n/** Files larger than this are routed through the GCS signed-URL flow. */\nconst LARGE_FILE_THRESHOLD_BYTES = 30 * 1024 * 1024;\nconst UPLOAD_TIMEOUT_MS = 120_000;\nconst SMALL_FILE_RETRY_DELAYS_MS = [600, 1800];\n\nfunction builderUploadHost(): string {\n return (\n process.env.BUILDER_APP_HOST ||\n process.env.BUILDER_PUBLIC_APP_HOST ||\n DEFAULT_BUILDER_APP_HOST\n );\n}\n\nfunction makeBody(bytes: Uint8Array, mimeType: string): BodyInit {\n return typeof Blob !== \"undefined\"\n ? new Blob([bytes as unknown as BlobPart], { type: mimeType })\n : (bytes as unknown as BodyInit);\n}\n\nfunction fetchWithTimeout(url: string, init: RequestInit): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), UPLOAD_TIMEOUT_MS);\n return fetch(url, { ...init, signal: controller.signal }).finally(() =>\n clearTimeout(timer),\n );\n}\n\nasync function assertOk(res: Response, label: string): Promise<void> {\n if (!res.ok) {\n const body = await res.text().catch(() => \"\");\n throw new Error(`${label} (${res.status}): ${body || res.statusText}`);\n }\n}\n\nasync function uploadLargeFileViaSignedUrl(\n input: FileUploadInput,\n privateKey: string,\n bareMimeType: string,\n bytes: Uint8Array,\n): Promise<FileUploadResult> {\n const name = input.filename ?? \"upload\";\n const mb = (bytes.byteLength / (1024 * 1024)).toFixed(1);\n\n console.log(\n `[builder-upload] large-file path: ${name} ${mb}MB ${bareMimeType}`,\n );\n\n // Step 1 — request a signed URL.\n console.log(`[builder-upload] step 1: requesting signed URL`);\n const { uploadUrl, assetId, requiredHeaders } = await requestBuilderSignedUrl(\n privateKey,\n name,\n bareMimeType,\n bytes.byteLength,\n );\n console.log(`[builder-upload] step 1 ok: assetId=${assetId}`);\n\n // Step 2 — PUT bytes directly to GCS. Only requiredHeaders; no Authorization\n // (signed URL carries its own auth — extra signed headers break the signature).\n console.log(`[builder-upload] step 2 [${assetId}]: PUT ${mb}MB to GCS`);\n const step2Res = await fetchWithTimeout(uploadUrl, {\n method: \"PUT\",\n headers: requiredHeaders,\n body: makeBody(bytes, bareMimeType),\n });\n await assertOk(step2Res, \"GCS upload failed\");\n console.log(\n `[builder-upload] step 2 ok [${assetId}]: GCS ${step2Res.status} etag=${step2Res.headers.get(\"etag\") ?? \"none\"}`,\n );\n\n // Step 3 — register the asset and get the CDN URL.\n console.log(\n `[builder-upload] step 3: registering asset - ${assetId}, ${input.filename}`,\n );\n const { url, id } = await completeBuilderUpload(\n privateKey,\n assetId,\n input.filename,\n );\n console.log(`[builder-upload] done [${assetId}]: ${url}`);\n return { url, id, provider: \"builder\" };\n}\n\nasync function requestBuilderSignedUrl(\n privateKey: string,\n filename: string,\n mimeType: string,\n size: number,\n resumable = false,\n): Promise<{\n uploadUrl: string;\n assetId: string;\n requiredHeaders: Record<string, string>;\n}> {\n const host = builderUploadHost();\n const url = new URL(\"/api/v1/upload/signed-url\", host);\n const res = await fetchWithTimeout(url.toString(), {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${privateKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n fileName: filename,\n contentType: mimeType,\n size,\n resumable,\n }),\n });\n await assertOk(res, \"Builder.io signed-URL request failed\");\n const json = (await res.json()) as {\n uploadUrl?: string;\n assetId?: string;\n requiredHeaders?: Record<string, string>;\n };\n if (!json.uploadUrl || !json.assetId || !json.requiredHeaders) {\n throw new Error(\n `Builder.io signed-URL response missing required fields: ${JSON.stringify(Object.keys(json))}`,\n );\n }\n return {\n uploadUrl: json.uploadUrl,\n assetId: json.assetId,\n requiredHeaders: json.requiredHeaders,\n };\n}\n\nasync function completeBuilderUpload(\n privateKey: string,\n assetId: string,\n filename: string | undefined,\n): Promise<{ url: string; id?: string }> {\n const host = builderUploadHost();\n const res = await fetchWithTimeout(\n new URL(\"/api/v1/upload/complete\", host).toString(),\n {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${privateKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ assetId, name: filename }),\n },\n );\n await assertOk(res, \"Builder.io upload complete failed\");\n const json = (await res.json()) as { url?: string; id?: string };\n if (!json.url) throw new Error(\"Builder.io upload/complete returned no URL\");\n return { url: json.url, id: json.id };\n}\n\n// Retry transient 5xx once with backoff. Builder.io's upload service\n// occasionally returns a bodyless 500 (\"Internal Error\") on the first\n// attempt — usually GCS write hiccups that succeed on retry.\nasync function uploadSmallFile(url: URL, init: RequestInit): Promise<Response> {\n let response: Response | null = null;\n let lastErrorBody = \"\";\n\n for (\n let attempt = 0;\n attempt <= SMALL_FILE_RETRY_DELAYS_MS.length;\n attempt++\n ) {\n const retryDelay = SMALL_FILE_RETRY_DELAYS_MS[attempt]; // undefined on last attempt\n try {\n response = await fetchWithTimeout(url.toString(), init);\n } catch (err) {\n if (!retryDelay) throw err;\n await new Promise((r) => setTimeout(r, retryDelay));\n continue;\n }\n if (response.ok) return response;\n lastErrorBody = await response.text().catch(() => \"\");\n const isTransient = response.status >= 500 && response.status !== 501;\n if (!isTransient || !retryDelay) break;\n await new Promise((r) => setTimeout(r, retryDelay));\n }\n\n const status = response?.status ?? 0;\n const statusText = response?.statusText ?? \"no response\";\n throw new Error(\n `Builder.io upload failed (${status}): ${lastErrorBody || statusText}`,\n );\n}\n\n/**\n * Built-in Builder.io file upload provider.\n * Uses the same BUILDER_PRIVATE_KEY as the browser/background-agent flows,\n * so connecting Builder once (via the sidebar \"Connect Builder\" action)\n * automatically enables file uploads.\n *\n * Upload API: https://www.builder.io/c/docs/upload-api\n */\nexport const builderFileUploadProvider: FileUploadProvider = {\n id: \"builder\",\n name: \"Builder.io\",\n isConfigured: () => !!process.env.BUILDER_PRIVATE_KEY,\n upload: async ({ data, filename, mimeType }: FileUploadInput) => {\n const { resolveBuilderPrivateKey } =\n await import(\"../server/credential-provider.js\");\n const privateKey = await resolveBuilderPrivateKey();\n if (!privateKey) {\n throw new Error(\"BUILDER_PRIVATE_KEY is not set\");\n }\n\n // Strip any media-type parameters (e.g. `;codecs=avc1,opus` from\n // MediaRecorder blobs) — Builder's upload API parses the body as raw\n // binary only when Content-Type is a bare MIME type. A parameterized\n // Content-Type falls through to the multipart/base64 paths which look\n // for an `image` field, and returns \"No image specified\" when it\n // doesn't find one.\n const bareMimeType = (mimeType || \"application/octet-stream\")\n .split(\";\")[0]\n .trim();\n\n const bytes =\n data instanceof Uint8Array ? data : new Uint8Array(data as any);\n const mb = (bytes.byteLength / (1024 * 1024)).toFixed(1);\n\n if (bytes.byteLength > LARGE_FILE_THRESHOLD_BYTES) {\n return uploadLargeFileViaSignedUrl(\n { data, filename, mimeType },\n privateKey,\n bareMimeType,\n bytes,\n );\n }\n\n console.log(\n `[builder-upload] small-file path: ${filename ?? \"upload\"} ${mb}MB ${bareMimeType}`,\n );\n\n const url = new URL(\"/api/v1/upload\", builderUploadHost());\n if (filename) url.searchParams.set(\"name\", filename);\n\n const response = await uploadSmallFile(url, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${privateKey}`,\n \"Content-Type\": bareMimeType,\n },\n body: makeBody(bytes, bareMimeType),\n });\n\n const json = (await response.json().catch(() => ({}))) as {\n url?: string;\n id?: string;\n };\n if (!json.url) throw new Error(\"Builder.io upload returned no URL\");\n\n console.log(`[builder-upload] done: ${json.url}`);\n return { url: json.url, id: json.id, provider: \"builder\" };\n },\n\n resumable: {\n async startSession(filename, mimeType, maxBytes) {\n const { resolveBuilderPrivateKey } =\n await import(\"../server/credential-provider.js\");\n const privateKey = await resolveBuilderPrivateKey();\n if (!privateKey) throw new Error(\"BUILDER_PRIVATE_KEY is not set\");\n\n console.log(\n `[builder-resumable] starting session: ${filename} ${mimeType} ${maxBytes} bytes`,\n );\n const { uploadUrl, assetId, requiredHeaders } =\n await requestBuilderSignedUrl(\n privateKey,\n filename,\n mimeType,\n maxBytes,\n true,\n );\n console.log(`[builder-resumable] session step 1 ok: assetId=${assetId}`);\n\n const initHeaders: Record<string, string> = {\n \"Content-Type\": mimeType,\n \"x-goog-resumable\": \"start\",\n };\n const contentLengthRange =\n requiredHeaders?.[\"x-goog-content-length-range\"];\n if (contentLengthRange)\n initHeaders[\"x-goog-content-length-range\"] = contentLengthRange;\n\n console.log(`[builder-resumable] session step 2: initiating GCS session`);\n const initRes = await fetchWithTimeout(uploadUrl, {\n method: \"POST\",\n headers: initHeaders,\n body: new Uint8Array(0),\n });\n if (!initRes.ok) {\n const body = await initRes.text().catch(() => \"\");\n throw new Error(\n `GCS resumable session initiation failed (${initRes.status}): ${body}`,\n );\n }\n const sessionUri = initRes.headers.get(\"location\");\n if (!sessionUri)\n throw new Error(\n \"GCS did not return a Location header for the resumable session\",\n );\n\n console.log(`[builder-resumable] session ready: assetId=${assetId}`);\n return {\n sessionId: sessionUri,\n meta: { assetId, filename, mimeType },\n } satisfies ResumableUploadSession;\n },\n\n async relayChunk(session, contentRange, bytes, options) {\n const sessionUri = session.sessionId;\n const MAX_ATTEMPTS = 4;\n const RETRYABLE = new Set([408, 429, 500, 502, 503, 504]);\n const delayMs = (attempt: number) =>\n Math.min(2000, 300 * 2 ** (attempt - 1));\n\n let lastError: unknown = null;\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n try {\n const headers: Record<string, string> = {\n \"Content-Range\": contentRange,\n };\n if (options?.mimeType) headers[\"Content-Type\"] = options.mimeType;\n const res = await fetch(sessionUri, {\n method: \"PUT\",\n headers,\n body: bytes as unknown as BodyInit,\n });\n if (res.status === 308 || res.ok)\n return {\n ok: true,\n status: res.status,\n } satisfies ResumableChunkResult;\n if (RETRYABLE.has(res.status) && attempt < MAX_ATTEMPTS) {\n await res.text().catch(() => \"\");\n console.warn(\n `[builder-resumable] transient ${res.status} on attempt ${attempt}, retrying`,\n );\n await new Promise((r) => setTimeout(r, delayMs(attempt)));\n continue;\n }\n return {\n ok: false,\n status: res.status,\n } satisfies ResumableChunkResult;\n } catch (err) {\n lastError = err;\n if (attempt >= MAX_ATTEMPTS) break;\n console.warn(\n `[builder-resumable] network error on attempt ${attempt}:`,\n err instanceof Error ? err.message : String(err),\n );\n await new Promise((r) => setTimeout(r, delayMs(attempt)));\n }\n }\n throw lastError instanceof Error\n ? lastError\n : new Error(\"GCS PUT failed after retries\");\n },\n\n async completeSession(session, filename) {\n const { resolveBuilderPrivateKey } =\n await import(\"../server/credential-provider.js\");\n const privateKey = await resolveBuilderPrivateKey();\n if (!privateKey) throw new Error(\"BUILDER_PRIVATE_KEY is not set\");\n\n const assetId = session.meta.assetId as string;\n console.log(`[builder-resumable] completing upload: assetId=${assetId}`);\n const { url } = await completeBuilderUpload(\n privateKey,\n assetId,\n filename,\n );\n console.log(`[builder-resumable] upload complete: ${url}`);\n return url;\n },\n },\n};\n"]}
|
|
1
|
+
{"version":3,"file":"builder.js","sourceRoot":"","sources":["../../src/file-upload/builder.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n FileUploadProvider,\n FileUploadInput,\n FileUploadResult,\n ResumableUploadSession,\n ResumableChunkResult,\n} from \"./types.js\";\n\nconst DEFAULT_BUILDER_APP_HOST = \"https://builder.io\";\n\n/** Files larger than this are routed through the GCS signed-URL flow. */\nconst LARGE_FILE_THRESHOLD_BYTES = 30 * 1024 * 1024;\nconst UPLOAD_TIMEOUT_MS = 120_000;\nconst SMALL_FILE_RETRY_DELAYS_MS = [600, 1800];\n\nfunction builderUploadHost(): string {\n return (\n process.env.BUILDER_APP_HOST ||\n process.env.BUILDER_PUBLIC_APP_HOST ||\n DEFAULT_BUILDER_APP_HOST\n );\n}\n\nfunction makeBody(bytes: Uint8Array, mimeType: string): BodyInit {\n return typeof Blob !== \"undefined\"\n ? new Blob([bytes as unknown as BlobPart], { type: mimeType })\n : (bytes as unknown as BodyInit);\n}\n\nfunction shouldUseSignedUrlUpload(\n bytes: Uint8Array,\n mimeType: string,\n): boolean {\n return (\n bytes.byteLength > LARGE_FILE_THRESHOLD_BYTES || /^video\\//i.test(mimeType)\n );\n}\n\nfunction fetchWithTimeout(url: string, init: RequestInit): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), UPLOAD_TIMEOUT_MS);\n return fetch(url, { ...init, signal: controller.signal }).finally(() =>\n clearTimeout(timer),\n );\n}\n\nasync function assertOk(res: Response, label: string): Promise<void> {\n if (!res.ok) {\n const body = await res.text().catch(() => \"\");\n throw new Error(`${label} (${res.status}): ${body || res.statusText}`);\n }\n}\n\nasync function uploadLargeFileViaSignedUrl(\n input: FileUploadInput,\n privateKey: string,\n bareMimeType: string,\n bytes: Uint8Array,\n): Promise<FileUploadResult> {\n const name = input.filename ?? \"upload\";\n const mb = (bytes.byteLength / (1024 * 1024)).toFixed(1);\n\n console.log(\n `[builder-upload] large-file path: ${name} ${mb}MB ${bareMimeType}`,\n );\n\n // Step 1 — request a signed URL.\n console.log(`[builder-upload] step 1: requesting signed URL`);\n const { uploadUrl, assetId, requiredHeaders } = await requestBuilderSignedUrl(\n privateKey,\n name,\n bareMimeType,\n bytes.byteLength,\n );\n console.log(`[builder-upload] step 1 ok: assetId=${assetId}`);\n\n // Step 2 — PUT bytes directly to GCS. Only requiredHeaders; no Authorization\n // (signed URL carries its own auth — extra signed headers break the signature).\n console.log(`[builder-upload] step 2 [${assetId}]: PUT ${mb}MB to GCS`);\n const step2Res = await fetchWithTimeout(uploadUrl, {\n method: \"PUT\",\n headers: requiredHeaders,\n body: makeBody(bytes, bareMimeType),\n });\n await assertOk(step2Res, \"GCS upload failed\");\n console.log(\n `[builder-upload] step 2 ok [${assetId}]: GCS ${step2Res.status} etag=${step2Res.headers.get(\"etag\") ?? \"none\"}`,\n );\n\n // Step 3 — register the asset and get the CDN URL.\n console.log(\n `[builder-upload] step 3: registering asset - ${assetId}, ${input.filename}`,\n );\n const { url, id } = await completeBuilderUpload(\n privateKey,\n assetId,\n input.filename,\n { skipCompressionWait: input.skipCompressionWait },\n );\n console.log(`[builder-upload] done [${assetId}]: ${url}`);\n return { url, id, provider: \"builder\" };\n}\n\nasync function requestBuilderSignedUrl(\n privateKey: string,\n filename: string,\n mimeType: string,\n size: number,\n resumable = false,\n): Promise<{\n uploadUrl: string;\n assetId: string;\n requiredHeaders: Record<string, string>;\n}> {\n const host = builderUploadHost();\n const url = new URL(\"/api/v1/upload/signed-url\", host);\n const res = await fetchWithTimeout(url.toString(), {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${privateKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n fileName: filename,\n contentType: mimeType,\n size,\n resumable,\n }),\n });\n await assertOk(res, \"Builder.io signed-URL request failed\");\n const json = (await res.json()) as {\n uploadUrl?: string;\n assetId?: string;\n requiredHeaders?: Record<string, string>;\n };\n if (!json.uploadUrl || !json.assetId || !json.requiredHeaders) {\n throw new Error(\n `Builder.io signed-URL response missing required fields: ${JSON.stringify(Object.keys(json))}`,\n );\n }\n return {\n uploadUrl: json.uploadUrl,\n assetId: json.assetId,\n requiredHeaders: json.requiredHeaders,\n };\n}\n\nasync function completeBuilderUpload(\n privateKey: string,\n assetId: string,\n filename: string | undefined,\n options?: { skipCompressionWait?: boolean },\n): Promise<{ url: string; id?: string }> {\n const host = builderUploadHost();\n const url = new URL(\"/api/v1/upload/complete\", host);\n if (options?.skipCompressionWait) {\n url.searchParams.set(\"skipCompressionWait\", \"true\");\n }\n const res = await fetchWithTimeout(url.toString(), {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${privateKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ assetId, name: filename }),\n });\n await assertOk(res, \"Builder.io upload complete failed\");\n const json = (await res.json()) as { url?: string; id?: string };\n if (!json.url) throw new Error(\"Builder.io upload/complete returned no URL\");\n return { url: json.url, id: json.id };\n}\n\n// Retry transient 5xx once with backoff. Builder.io's upload service\n// occasionally returns a bodyless 500 (\"Internal Error\") on the first\n// attempt — usually GCS write hiccups that succeed on retry.\nasync function uploadSmallFile(url: URL, init: RequestInit): Promise<Response> {\n let response: Response | null = null;\n let lastErrorBody = \"\";\n\n for (\n let attempt = 0;\n attempt <= SMALL_FILE_RETRY_DELAYS_MS.length;\n attempt++\n ) {\n const retryDelay = SMALL_FILE_RETRY_DELAYS_MS[attempt]; // undefined on last attempt\n try {\n response = await fetchWithTimeout(url.toString(), init);\n } catch (err) {\n if (!retryDelay) throw err;\n await new Promise((r) => setTimeout(r, retryDelay));\n continue;\n }\n if (response.ok) return response;\n lastErrorBody = await response.text().catch(() => \"\");\n const isTransient = response.status >= 500 && response.status !== 501;\n if (!isTransient || !retryDelay) break;\n await new Promise((r) => setTimeout(r, retryDelay));\n }\n\n const status = response?.status ?? 0;\n const statusText = response?.statusText ?? \"no response\";\n throw new Error(\n `Builder.io upload failed (${status}): ${lastErrorBody || statusText}`,\n );\n}\n\n/**\n * Built-in Builder.io file upload provider.\n * Uses the same BUILDER_PRIVATE_KEY as the browser/background-agent flows,\n * so connecting Builder once (via the sidebar \"Connect Builder\" action)\n * automatically enables file uploads.\n *\n * Upload API: https://www.builder.io/c/docs/upload-api\n */\nexport const builderFileUploadProvider: FileUploadProvider = {\n id: \"builder\",\n name: \"Builder.io\",\n isConfigured: () => !!process.env.BUILDER_PRIVATE_KEY,\n upload: async (input: FileUploadInput) => {\n const { data, filename, mimeType } = input;\n const { resolveBuilderPrivateKey } =\n await import(\"../server/credential-provider.js\");\n const privateKey = await resolveBuilderPrivateKey();\n if (!privateKey) {\n throw new Error(\"BUILDER_PRIVATE_KEY is not set\");\n }\n\n // Strip any media-type parameters (e.g. `;codecs=avc1,opus` from\n // MediaRecorder blobs) — Builder's upload API parses the body as raw\n // binary only when Content-Type is a bare MIME type. A parameterized\n // Content-Type falls through to the multipart/base64 paths which look\n // for an `image` field, and returns \"No image specified\" when it\n // doesn't find one.\n const bareMimeType = (mimeType || \"application/octet-stream\")\n .split(\";\")[0]\n .trim();\n\n const bytes =\n data instanceof Uint8Array ? data : new Uint8Array(data as any);\n const mb = (bytes.byteLength / (1024 * 1024)).toFixed(1);\n\n if (shouldUseSignedUrlUpload(bytes, bareMimeType)) {\n return uploadLargeFileViaSignedUrl(\n input,\n privateKey,\n bareMimeType,\n bytes,\n );\n }\n\n console.log(\n `[builder-upload] small-file path: ${filename ?? \"upload\"} ${mb}MB ${bareMimeType}`,\n );\n\n const url = new URL(\"/api/v1/upload\", builderUploadHost());\n if (filename) url.searchParams.set(\"name\", filename);\n if (input.skipCompressionWait) {\n url.searchParams.set(\"skipCompressionWait\", \"true\");\n }\n\n const response = await uploadSmallFile(url, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${privateKey}`,\n \"Content-Type\": bareMimeType,\n },\n body: makeBody(bytes, bareMimeType),\n });\n\n const json = (await response.json().catch(() => ({}))) as {\n url?: string;\n id?: string;\n };\n if (!json.url) throw new Error(\"Builder.io upload returned no URL\");\n\n console.log(`[builder-upload] done: ${json.url}`);\n return { url: json.url, id: json.id, provider: \"builder\" };\n },\n\n resumable: {\n async startSession(filename, mimeType, maxBytes) {\n const { resolveBuilderPrivateKey } =\n await import(\"../server/credential-provider.js\");\n const privateKey = await resolveBuilderPrivateKey();\n if (!privateKey) throw new Error(\"BUILDER_PRIVATE_KEY is not set\");\n\n console.log(\n `[builder-resumable] starting session: ${filename} ${mimeType} ${maxBytes} bytes`,\n );\n const { uploadUrl, assetId, requiredHeaders } =\n await requestBuilderSignedUrl(\n privateKey,\n filename,\n mimeType,\n maxBytes,\n true,\n );\n console.log(`[builder-resumable] session step 1 ok: assetId=${assetId}`);\n\n const initHeaders: Record<string, string> = {\n \"Content-Type\": mimeType,\n \"x-goog-resumable\": \"start\",\n };\n const contentLengthRange =\n requiredHeaders?.[\"x-goog-content-length-range\"];\n if (contentLengthRange)\n initHeaders[\"x-goog-content-length-range\"] = contentLengthRange;\n\n console.log(`[builder-resumable] session step 2: initiating GCS session`);\n const initRes = await fetchWithTimeout(uploadUrl, {\n method: \"POST\",\n headers: initHeaders,\n body: new Uint8Array(0),\n });\n if (!initRes.ok) {\n const body = await initRes.text().catch(() => \"\");\n throw new Error(\n `GCS resumable session initiation failed (${initRes.status}): ${body}`,\n );\n }\n const sessionUri = initRes.headers.get(\"location\");\n if (!sessionUri)\n throw new Error(\n \"GCS did not return a Location header for the resumable session\",\n );\n\n console.log(`[builder-resumable] session ready: assetId=${assetId}`);\n return {\n sessionId: sessionUri,\n meta: { assetId, filename, mimeType },\n } satisfies ResumableUploadSession;\n },\n\n async relayChunk(session, contentRange, bytes, options) {\n const sessionUri = session.sessionId;\n const MAX_ATTEMPTS = 4;\n const RETRYABLE = new Set([408, 429, 500, 502, 503, 504]);\n const delayMs = (attempt: number) =>\n Math.min(2000, 300 * 2 ** (attempt - 1));\n\n let lastError: unknown = null;\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n try {\n const headers: Record<string, string> = {\n \"Content-Range\": contentRange,\n };\n if (options?.mimeType) headers[\"Content-Type\"] = options.mimeType;\n const res = await fetch(sessionUri, {\n method: \"PUT\",\n headers,\n body: bytes as unknown as BodyInit,\n });\n if (res.status === 308 || res.ok)\n return {\n ok: true,\n status: res.status,\n } satisfies ResumableChunkResult;\n if (RETRYABLE.has(res.status) && attempt < MAX_ATTEMPTS) {\n await res.text().catch(() => \"\");\n console.warn(\n `[builder-resumable] transient ${res.status} on attempt ${attempt}, retrying`,\n );\n await new Promise((r) => setTimeout(r, delayMs(attempt)));\n continue;\n }\n return {\n ok: false,\n status: res.status,\n } satisfies ResumableChunkResult;\n } catch (err) {\n lastError = err;\n if (attempt >= MAX_ATTEMPTS) break;\n console.warn(\n `[builder-resumable] network error on attempt ${attempt}:`,\n err instanceof Error ? err.message : String(err),\n );\n await new Promise((r) => setTimeout(r, delayMs(attempt)));\n }\n }\n throw lastError instanceof Error\n ? lastError\n : new Error(\"GCS PUT failed after retries\");\n },\n\n async completeSession(session, filename, options) {\n const { resolveBuilderPrivateKey } =\n await import(\"../server/credential-provider.js\");\n const privateKey = await resolveBuilderPrivateKey();\n if (!privateKey) throw new Error(\"BUILDER_PRIVATE_KEY is not set\");\n\n const assetId = session.meta.assetId as string;\n console.log(`[builder-resumable] completing upload: assetId=${assetId}`);\n const { url } = await completeBuilderUpload(\n privateKey,\n assetId,\n filename,\n {\n skipCompressionWait:\n options?.skipCompressionWait ||\n session.meta.skipCompressionWait === true,\n },\n );\n console.log(`[builder-resumable] upload complete: ${url}`);\n return url;\n },\n },\n};\n"]}
|
|
@@ -15,6 +15,8 @@ export interface FileUploadInput {
|
|
|
15
15
|
mimeType?: string;
|
|
16
16
|
/** Optional owner email for per-user scoping in fallback storage. */
|
|
17
17
|
ownerEmail?: string;
|
|
18
|
+
/** Builder.io upload hint: return after asset registration instead of waiting for server-side compression. */
|
|
19
|
+
skipCompressionWait?: boolean;
|
|
18
20
|
}
|
|
19
21
|
export interface FileUploadResult {
|
|
20
22
|
/** Public URL where the file can be fetched. */
|
|
@@ -62,7 +64,9 @@ export interface FileUploadProvider {
|
|
|
62
64
|
relayChunk(session: ResumableUploadSession, contentRange: string, bytes: Uint8Array, options?: {
|
|
63
65
|
mimeType?: string;
|
|
64
66
|
}): Promise<ResumableChunkResult>;
|
|
65
|
-
completeSession(session: ResumableUploadSession, filename: string
|
|
67
|
+
completeSession(session: ResumableUploadSession, filename: string, options?: {
|
|
68
|
+
skipCompressionWait?: boolean;
|
|
69
|
+
}): Promise<string>;
|
|
66
70
|
};
|
|
67
71
|
}
|
|
68
72
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/file-upload/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,eAAe;IAC9B,qBAAqB;IACrB,IAAI,EAAE,UAAU,GAAG,MAAM,CAAC;IAC1B,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/file-upload/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,eAAe;IAC9B,qBAAqB;IACrB,IAAI,EAAE,UAAU,GAAG,MAAM,CAAC;IAC1B,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8GAA8G;IAC9G,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,gBAAgB;IAC/B,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,0EAA0E;IAC1E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;qFAEqF;AACrF,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf;uEACmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,kBAAkB;IACjC,uCAAuC;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,YAAY,EAAE,MAAM,OAAO,CAAC;IAC5B;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAChD,wDAAwD;IACxD,MAAM,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC9D;;;;OAIG;IACH,SAAS,CAAC,EAAE;QACV,YAAY,CACV,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,sBAAsB,CAAC,CAAC;QACnC,UAAU,CACR,OAAO,EAAE,sBAAsB,EAC/B,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,UAAU,EACjB,OAAO,CAAC,EAAE;YAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,GAC9B,OAAO,CAAC,oBAAoB,CAAC,CAAC;QACjC,eAAe,CACb,OAAO,EAAE,sBAAsB,EAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;YAAE,mBAAmB,CAAC,EAAE,OAAO,CAAA;SAAE,GAC1C,OAAO,CAAC,MAAM,CAAC,CAAC;KACpB,CAAC;CACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/file-upload/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG","sourcesContent":["/**\n * File upload framework primitive.\n *\n * Templates call `uploadFile()` to upload a file and get back a URL.\n * The framework dispatches to whichever provider is registered (Builder.io\n * built-in, or a user-supplied one). If no provider is active, it falls back\n * to the SQL resources store — fine for dev, not recommended for production.\n */\n\nexport interface FileUploadInput {\n /** File contents. */\n data: Uint8Array | Buffer;\n /** Original filename, used for extension/display. */\n filename?: string;\n /** MIME type, e.g. \"image/png\". */\n mimeType?: string;\n /** Optional owner email for per-user scoping in fallback storage. */\n ownerEmail?: string;\n}\n\nexport interface FileUploadResult {\n /** Public URL where the file can be fetched. */\n url: string;\n /** Optional provider-specific id (e.g. resource id, Builder asset id). */\n id?: string;\n /** The provider that handled the upload. */\n provider: string;\n}\n\n/** Opaque session handle returned by {@link FileUploadProvider.resumable.startSession}.\n * `sessionId` is provider-specific (GCS Location URI, S3 UploadId, etc.).\n * `meta` holds any provider state needed for subsequent relay and complete calls. */\nexport interface ResumableUploadSession {\n sessionId: string;\n meta: Record<string, unknown>;\n}\n\nexport interface ResumableChunkResult {\n ok: boolean;\n status: number;\n /** Providers that need per-chunk state (e.g. S3 ETags) return updated meta\n * here; the chunk route merges it back into the stored session. */\n updatedMeta?: Record<string, unknown>;\n}\n\nexport interface FileUploadProvider {\n /** Unique id, e.g. \"builder\", \"s3\". */\n id: string;\n /** Human-readable name. */\n name: string;\n /** Returns true if this provider is configured from synchronous runtime state. */\n isConfigured: () => boolean;\n /**\n * Returns true if this provider is configured for the active request.\n * Use for DB-backed user/org/workspace secrets that require request context.\n */\n isConfiguredForRequest?: () => Promise<boolean>;\n /** Upload a file and return a URL. Throw on failure. */\n upload: (input: FileUploadInput) => Promise<FileUploadResult>;\n /**\n * Optional resumable/streaming upload capability.\n * When present, create-recording will initialise a session and stream chunks\n * during recording instead of assembling the full blob after stop().\n */\n resumable?: {\n startSession(\n filename: string,\n mimeType: string,\n maxBytes: number,\n ): Promise<ResumableUploadSession>;\n relayChunk(\n session: ResumableUploadSession,\n contentRange: string,\n bytes: Uint8Array,\n options?: { mimeType?: string },\n ): Promise<ResumableChunkResult>;\n completeSession(\n session: ResumableUploadSession,\n filename: string,\n ): Promise<string>;\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/file-upload/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG","sourcesContent":["/**\n * File upload framework primitive.\n *\n * Templates call `uploadFile()` to upload a file and get back a URL.\n * The framework dispatches to whichever provider is registered (Builder.io\n * built-in, or a user-supplied one). If no provider is active, it falls back\n * to the SQL resources store — fine for dev, not recommended for production.\n */\n\nexport interface FileUploadInput {\n /** File contents. */\n data: Uint8Array | Buffer;\n /** Original filename, used for extension/display. */\n filename?: string;\n /** MIME type, e.g. \"image/png\". */\n mimeType?: string;\n /** Optional owner email for per-user scoping in fallback storage. */\n ownerEmail?: string;\n /** Builder.io upload hint: return after asset registration instead of waiting for server-side compression. */\n skipCompressionWait?: boolean;\n}\n\nexport interface FileUploadResult {\n /** Public URL where the file can be fetched. */\n url: string;\n /** Optional provider-specific id (e.g. resource id, Builder asset id). */\n id?: string;\n /** The provider that handled the upload. */\n provider: string;\n}\n\n/** Opaque session handle returned by {@link FileUploadProvider.resumable.startSession}.\n * `sessionId` is provider-specific (GCS Location URI, S3 UploadId, etc.).\n * `meta` holds any provider state needed for subsequent relay and complete calls. */\nexport interface ResumableUploadSession {\n sessionId: string;\n meta: Record<string, unknown>;\n}\n\nexport interface ResumableChunkResult {\n ok: boolean;\n status: number;\n /** Providers that need per-chunk state (e.g. S3 ETags) return updated meta\n * here; the chunk route merges it back into the stored session. */\n updatedMeta?: Record<string, unknown>;\n}\n\nexport interface FileUploadProvider {\n /** Unique id, e.g. \"builder\", \"s3\". */\n id: string;\n /** Human-readable name. */\n name: string;\n /** Returns true if this provider is configured from synchronous runtime state. */\n isConfigured: () => boolean;\n /**\n * Returns true if this provider is configured for the active request.\n * Use for DB-backed user/org/workspace secrets that require request context.\n */\n isConfiguredForRequest?: () => Promise<boolean>;\n /** Upload a file and return a URL. Throw on failure. */\n upload: (input: FileUploadInput) => Promise<FileUploadResult>;\n /**\n * Optional resumable/streaming upload capability.\n * When present, create-recording will initialise a session and stream chunks\n * during recording instead of assembling the full blob after stop().\n */\n resumable?: {\n startSession(\n filename: string,\n mimeType: string,\n maxBytes: number,\n ): Promise<ResumableUploadSession>;\n relayChunk(\n session: ResumableUploadSession,\n contentRange: string,\n bytes: Uint8Array,\n options?: { mimeType?: string },\n ): Promise<ResumableChunkResult>;\n completeSession(\n session: ResumableUploadSession,\n filename: string,\n options?: { skipCompressionWait?: boolean },\n ): Promise<string>;\n };\n}\n"]}
|