@agent-native/creative-context 0.0.0-beta-20260820202238
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -0
- package/agent-native.eject.json +41 -0
- package/agent-native.package.json +119 -0
- package/dist/actions/confirm-canonical-logo.d.ts +6 -0
- package/dist/actions/confirm-canonical-logo.d.ts.map +1 -0
- package/dist/actions/confirm-canonical-logo.js +19 -0
- package/dist/actions/confirm-canonical-logo.js.map +1 -0
- package/dist/actions/continue-context-import.d.ts +22 -0
- package/dist/actions/continue-context-import.d.ts.map +1 -0
- package/dist/actions/continue-context-import.js +13 -0
- package/dist/actions/continue-context-import.js.map +1 -0
- package/dist/actions/creative-context-a2a.d.ts +10 -0
- package/dist/actions/creative-context-a2a.d.ts.map +1 -0
- package/dist/actions/creative-context-a2a.js +66 -0
- package/dist/actions/creative-context-a2a.js.map +1 -0
- package/dist/actions/get-brand-profile.d.ts +33 -0
- package/dist/actions/get-brand-profile.d.ts.map +1 -0
- package/dist/actions/get-brand-profile.js +13 -0
- package/dist/actions/get-brand-profile.js.map +1 -0
- package/dist/actions/get-context-import-status.d.ts +22 -0
- package/dist/actions/get-context-import-status.d.ts.map +1 -0
- package/dist/actions/get-context-import-status.js +12 -0
- package/dist/actions/get-context-import-status.js.map +1 -0
- package/dist/actions/get-context-item.d.ts +2 -0
- package/dist/actions/get-context-item.d.ts.map +1 -0
- package/dist/actions/get-context-item.js +2 -0
- package/dist/actions/get-context-item.js.map +1 -0
- package/dist/actions/get-context-pack.d.ts +7 -0
- package/dist/actions/get-context-pack.d.ts.map +1 -0
- package/dist/actions/get-context-pack.js +12 -0
- package/dist/actions/get-context-pack.js.map +1 -0
- package/dist/actions/get-creative-context.d.ts +157 -0
- package/dist/actions/get-creative-context.d.ts.map +1 -0
- package/dist/actions/get-creative-context.js +148 -0
- package/dist/actions/get-creative-context.js.map +1 -0
- package/dist/actions/get-google-picker-session.d.ts +10 -0
- package/dist/actions/get-google-picker-session.d.ts.map +1 -0
- package/dist/actions/get-google-picker-session.js +38 -0
- package/dist/actions/get-google-picker-session.js.map +1 -0
- package/dist/actions/index.d.ts +3 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +73 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/actions/infer-brand-dna.d.ts +52 -0
- package/dist/actions/infer-brand-dna.d.ts.map +1 -0
- package/dist/actions/infer-brand-dna.js +13 -0
- package/dist/actions/infer-brand-dna.js.map +1 -0
- package/dist/actions/list-canonical-logo-candidates.d.ts +9 -0
- package/dist/actions/list-canonical-logo-candidates.d.ts.map +1 -0
- package/dist/actions/list-canonical-logo-candidates.js +82 -0
- package/dist/actions/list-canonical-logo-candidates.js.map +1 -0
- package/dist/actions/list-context-connections.d.ts +22 -0
- package/dist/actions/list-context-connections.d.ts.map +1 -0
- package/dist/actions/list-context-connections.js +43 -0
- package/dist/actions/list-context-connections.js.map +1 -0
- package/dist/actions/list-context-memberships.d.ts +11 -0
- package/dist/actions/list-context-memberships.d.ts.map +1 -0
- package/dist/actions/list-context-memberships.js +47 -0
- package/dist/actions/list-context-memberships.js.map +1 -0
- package/dist/actions/list-context-packs.d.ts +10 -0
- package/dist/actions/list-context-packs.d.ts.map +1 -0
- package/dist/actions/list-context-packs.js +16 -0
- package/dist/actions/list-context-packs.js.map +1 -0
- package/dist/actions/list-context-sources.d.ts +12 -0
- package/dist/actions/list-context-sources.d.ts.map +1 -0
- package/dist/actions/list-context-sources.js +20 -0
- package/dist/actions/list-context-sources.js.map +1 -0
- package/dist/actions/list-context-suggestions.d.ts +13 -0
- package/dist/actions/list-context-suggestions.d.ts.map +1 -0
- package/dist/actions/list-context-suggestions.js +28 -0
- package/dist/actions/list-context-suggestions.js.map +1 -0
- package/dist/actions/list-creative-contexts.d.ts +12 -0
- package/dist/actions/list-creative-contexts.d.ts.map +1 -0
- package/dist/actions/list-creative-contexts.js +28 -0
- package/dist/actions/list-creative-contexts.js.map +1 -0
- package/dist/actions/manage-brand-profile.d.ts +28 -0
- package/dist/actions/manage-brand-profile.d.ts.map +1 -0
- package/dist/actions/manage-brand-profile.js +47 -0
- package/dist/actions/manage-brand-profile.js.map +1 -0
- package/dist/actions/manage-context-membership.d.ts +64 -0
- package/dist/actions/manage-context-membership.d.ts.map +1 -0
- package/dist/actions/manage-context-membership.js +70 -0
- package/dist/actions/manage-context-membership.js.map +1 -0
- package/dist/actions/manage-context-pack.d.ts +36 -0
- package/dist/actions/manage-context-pack.d.ts.map +1 -0
- package/dist/actions/manage-context-pack.js +71 -0
- package/dist/actions/manage-context-pack.js.map +1 -0
- package/dist/actions/manage-context-source.d.ts +51 -0
- package/dist/actions/manage-context-source.d.ts.map +1 -0
- package/dist/actions/manage-context-source.js +130 -0
- package/dist/actions/manage-context-source.js.map +1 -0
- package/dist/actions/manage-creative-context.d.ts +28 -0
- package/dist/actions/manage-creative-context.d.ts.map +1 -0
- package/dist/actions/manage-creative-context.js +65 -0
- package/dist/actions/manage-creative-context.js.map +1 -0
- package/dist/actions/manage-layout-template.d.ts +13 -0
- package/dist/actions/manage-layout-template.d.ts.map +1 -0
- package/dist/actions/manage-layout-template.js +43 -0
- package/dist/actions/manage-layout-template.js.map +1 -0
- package/dist/actions/preview-context-import.d.ts +23 -0
- package/dist/actions/preview-context-import.d.ts.map +1 -0
- package/dist/actions/preview-context-import.js +57 -0
- package/dist/actions/preview-context-import.js.map +1 -0
- package/dist/actions/process-context-purge.d.ts +37 -0
- package/dist/actions/process-context-purge.d.ts.map +1 -0
- package/dist/actions/process-context-purge.js +20 -0
- package/dist/actions/process-context-purge.js.map +1 -0
- package/dist/actions/propose-brand-dna.d.ts +15 -0
- package/dist/actions/propose-brand-dna.d.ts.map +1 -0
- package/dist/actions/propose-brand-dna.js +19 -0
- package/dist/actions/propose-brand-dna.js.map +1 -0
- package/dist/actions/propose-canonical-logo.d.ts +9 -0
- package/dist/actions/propose-canonical-logo.d.ts.map +1 -0
- package/dist/actions/propose-canonical-logo.js +16 -0
- package/dist/actions/propose-canonical-logo.js.map +1 -0
- package/dist/actions/publish-brand-dna.d.ts +13 -0
- package/dist/actions/publish-brand-dna.d.ts.map +1 -0
- package/dist/actions/publish-brand-dna.js +23 -0
- package/dist/actions/publish-brand-dna.js.map +1 -0
- package/dist/actions/recommend-context-roots.d.ts +14 -0
- package/dist/actions/recommend-context-roots.d.ts.map +1 -0
- package/dist/actions/recommend-context-roots.js +22 -0
- package/dist/actions/recommend-context-roots.js.map +1 -0
- package/dist/actions/record-context-feedback.d.ts +10 -0
- package/dist/actions/record-context-feedback.d.ts.map +1 -0
- package/dist/actions/record-context-feedback.js +14 -0
- package/dist/actions/record-context-feedback.js.map +1 -0
- package/dist/actions/review-context-items.d.ts +35 -0
- package/dist/actions/review-context-items.d.ts.map +1 -0
- package/dist/actions/review-context-items.js +65 -0
- package/dist/actions/review-context-items.js.map +1 -0
- package/dist/actions/run-embedding-bakeoff.d.ts +15 -0
- package/dist/actions/run-embedding-bakeoff.d.ts.map +1 -0
- package/dist/actions/run-embedding-bakeoff.js +72 -0
- package/dist/actions/run-embedding-bakeoff.js.map +1 -0
- package/dist/actions/search-creative-context.d.ts +80 -0
- package/dist/actions/search-creative-context.d.ts.map +1 -0
- package/dist/actions/search-creative-context.js +42 -0
- package/dist/actions/search-creative-context.js.map +1 -0
- package/dist/actions/set-brand-dna.d.ts +15 -0
- package/dist/actions/set-brand-dna.d.ts.map +1 -0
- package/dist/actions/set-brand-dna.js +19 -0
- package/dist/actions/set-brand-dna.js.map +1 -0
- package/dist/actions/start-context-enrichment.d.ts +25 -0
- package/dist/actions/start-context-enrichment.d.ts.map +1 -0
- package/dist/actions/start-context-enrichment.js +48 -0
- package/dist/actions/start-context-enrichment.js.map +1 -0
- package/dist/actions/start-context-import.d.ts +24 -0
- package/dist/actions/start-context-import.d.ts.map +1 -0
- package/dist/actions/start-context-import.js +37 -0
- package/dist/actions/start-context-import.js.map +1 -0
- package/dist/client/CreativeContextChip.d.ts +18 -0
- package/dist/client/CreativeContextChip.d.ts.map +1 -0
- package/dist/client/CreativeContextChip.js +80 -0
- package/dist/client/CreativeContextChip.js.map +1 -0
- package/dist/client/CreativeContextPanel.d.ts +73 -0
- package/dist/client/CreativeContextPanel.d.ts.map +1 -0
- package/dist/client/CreativeContextPanel.js +1171 -0
- package/dist/client/CreativeContextPanel.js.map +1 -0
- package/dist/client/CreativeContextSettingsLink.d.ts +4 -0
- package/dist/client/CreativeContextSettingsLink.d.ts.map +1 -0
- package/dist/client/CreativeContextSettingsLink.js +8 -0
- package/dist/client/CreativeContextSettingsLink.js.map +1 -0
- package/dist/client/CreativeContextShareTab.d.ts +56 -0
- package/dist/client/CreativeContextShareTab.d.ts.map +1 -0
- package/dist/client/CreativeContextShareTab.js +213 -0
- package/dist/client/CreativeContextShareTab.js.map +1 -0
- package/dist/client/actions.d.ts +474 -0
- package/dist/client/actions.d.ts.map +1 -0
- package/dist/client/actions.js +411 -0
- package/dist/client/actions.js.map +1 -0
- package/dist/client/agent-tab.d.ts +10 -0
- package/dist/client/agent-tab.d.ts.map +1 -0
- package/dist/client/agent-tab.js +32 -0
- package/dist/client/agent-tab.js.map +1 -0
- package/dist/client/application-state.d.ts +24 -0
- package/dist/client/application-state.d.ts.map +1 -0
- package/dist/client/application-state.js +111 -0
- package/dist/client/application-state.js.map +1 -0
- package/dist/client/google-slides-picker.d.ts +19 -0
- package/dist/client/google-slides-picker.d.ts.map +1 -0
- package/dist/client/google-slides-picker.js +106 -0
- package/dist/client/google-slides-picker.js.map +1 -0
- package/dist/client/index.d.ts +10 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +10 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/messages.d.ts +5 -0
- package/dist/client/messages.d.ts.map +1 -0
- package/dist/client/messages.js +1158 -0
- package/dist/client/messages.js.map +1 -0
- package/dist/connectors/context.d.ts +9 -0
- package/dist/connectors/context.d.ts.map +1 -0
- package/dist/connectors/context.js +59 -0
- package/dist/connectors/context.js.map +1 -0
- package/dist/connectors/document-parser.d.ts +2 -0
- package/dist/connectors/document-parser.d.ts.map +1 -0
- package/dist/connectors/document-parser.js +2 -0
- package/dist/connectors/document-parser.js.map +1 -0
- package/dist/connectors/figma-native.d.ts +18 -0
- package/dist/connectors/figma-native.d.ts.map +1 -0
- package/dist/connectors/figma-native.js +883 -0
- package/dist/connectors/figma-native.js.map +1 -0
- package/dist/connectors/figma.d.ts +10 -0
- package/dist/connectors/figma.d.ts.map +1 -0
- package/dist/connectors/figma.js +166 -0
- package/dist/connectors/figma.js.map +1 -0
- package/dist/connectors/google-slides-native.d.ts +58 -0
- package/dist/connectors/google-slides-native.d.ts.map +1 -0
- package/dist/connectors/google-slides-native.js +1118 -0
- package/dist/connectors/google-slides-native.js.map +1 -0
- package/dist/connectors/google-slides.d.ts +15 -0
- package/dist/connectors/google-slides.d.ts.map +1 -0
- package/dist/connectors/google-slides.js +895 -0
- package/dist/connectors/google-slides.js.map +1 -0
- package/dist/connectors/index.d.ts +21 -0
- package/dist/connectors/index.d.ts.map +1 -0
- package/dist/connectors/index.js +45 -0
- package/dist/connectors/index.js.map +1 -0
- package/dist/connectors/manual.d.ts +9 -0
- package/dist/connectors/manual.d.ts.map +1 -0
- package/dist/connectors/manual.js +91 -0
- package/dist/connectors/manual.js.map +1 -0
- package/dist/connectors/normalize.d.ts +42 -0
- package/dist/connectors/normalize.d.ts.map +1 -0
- package/dist/connectors/normalize.js +335 -0
- package/dist/connectors/normalize.js.map +1 -0
- package/dist/connectors/notion.d.ts +10 -0
- package/dist/connectors/notion.d.ts.map +1 -0
- package/dist/connectors/notion.js +393 -0
- package/dist/connectors/notion.js.map +1 -0
- package/dist/connectors/private-artifacts.d.ts +39 -0
- package/dist/connectors/private-artifacts.d.ts.map +1 -0
- package/dist/connectors/private-artifacts.js +175 -0
- package/dist/connectors/private-artifacts.js.map +1 -0
- package/dist/connectors/provider-response.d.ts +20 -0
- package/dist/connectors/provider-response.d.ts.map +1 -0
- package/dist/connectors/provider-response.js +98 -0
- package/dist/connectors/provider-response.js.map +1 -0
- package/dist/connectors/recommendations.d.ts +25 -0
- package/dist/connectors/recommendations.d.ts.map +1 -0
- package/dist/connectors/recommendations.js +172 -0
- package/dist/connectors/recommendations.js.map +1 -0
- package/dist/connectors/registry.d.ts +10 -0
- package/dist/connectors/registry.d.ts.map +1 -0
- package/dist/connectors/registry.js +34 -0
- package/dist/connectors/registry.js.map +1 -0
- package/dist/connectors/rendered-design.d.ts +68 -0
- package/dist/connectors/rendered-design.d.ts.map +1 -0
- package/dist/connectors/rendered-design.js +362 -0
- package/dist/connectors/rendered-design.js.map +1 -0
- package/dist/connectors/rendered-page.d.ts +115 -0
- package/dist/connectors/rendered-page.d.ts.map +1 -0
- package/dist/connectors/rendered-page.js +1036 -0
- package/dist/connectors/rendered-page.js.map +1 -0
- package/dist/connectors/smart-defaults.d.ts +9 -0
- package/dist/connectors/smart-defaults.d.ts.map +1 -0
- package/dist/connectors/smart-defaults.js +72 -0
- package/dist/connectors/smart-defaults.js.map +1 -0
- package/dist/connectors/types.d.ts +79 -0
- package/dist/connectors/types.d.ts.map +1 -0
- package/dist/connectors/types.js +2 -0
- package/dist/connectors/types.js.map +1 -0
- package/dist/connectors/upload.d.ts +9 -0
- package/dist/connectors/upload.d.ts.map +1 -0
- package/dist/connectors/upload.js +440 -0
- package/dist/connectors/upload.js.map +1 -0
- package/dist/connectors/website.d.ts +20 -0
- package/dist/connectors/website.d.ts.map +1 -0
- package/dist/connectors/website.js +616 -0
- package/dist/connectors/website.js.map +1 -0
- package/dist/embeddings/index.d.ts +3 -0
- package/dist/embeddings/index.d.ts.map +1 -0
- package/dist/embeddings/index.js +3 -0
- package/dist/embeddings/index.js.map +1 -0
- package/dist/embeddings/providers.d.ts +2 -0
- package/dist/embeddings/providers.d.ts.map +1 -0
- package/dist/embeddings/providers.js +2 -0
- package/dist/embeddings/providers.js.map +1 -0
- package/dist/embeddings/types.d.ts +2 -0
- package/dist/embeddings/types.d.ts.map +1 -0
- package/dist/embeddings/types.js +2 -0
- package/dist/embeddings/types.js.map +1 -0
- package/dist/eval/acceptance-fixtures.d.ts +3 -0
- package/dist/eval/acceptance-fixtures.d.ts.map +1 -0
- package/dist/eval/acceptance-fixtures.js +84 -0
- package/dist/eval/acceptance-fixtures.js.map +1 -0
- package/dist/eval/acceptance.d.ts +114 -0
- package/dist/eval/acceptance.d.ts.map +1 -0
- package/dist/eval/acceptance.js +260 -0
- package/dist/eval/acceptance.js.map +1 -0
- package/dist/eval/fixtures.d.ts +16 -0
- package/dist/eval/fixtures.d.ts.map +1 -0
- package/dist/eval/fixtures.js +119 -0
- package/dist/eval/fixtures.js.map +1 -0
- package/dist/eval/index.d.ts +63 -0
- package/dist/eval/index.d.ts.map +1 -0
- package/dist/eval/index.js +174 -0
- package/dist/eval/index.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/jobs/background-worker.d.ts +31 -0
- package/dist/jobs/background-worker.d.ts.map +1 -0
- package/dist/jobs/background-worker.js +315 -0
- package/dist/jobs/background-worker.js.map +1 -0
- package/dist/jobs/import-runner.d.ts +3 -0
- package/dist/jobs/import-runner.d.ts.map +1 -0
- package/dist/jobs/import-runner.js +613 -0
- package/dist/jobs/import-runner.js.map +1 -0
- package/dist/jobs/index.d.ts +9 -0
- package/dist/jobs/index.d.ts.map +1 -0
- package/dist/jobs/index.js +8 -0
- package/dist/jobs/index.js.map +1 -0
- package/dist/jobs/progress.d.ts +3 -0
- package/dist/jobs/progress.d.ts.map +1 -0
- package/dist/jobs/progress.js +30 -0
- package/dist/jobs/progress.js.map +1 -0
- package/dist/jobs/rebuild.d.ts +13 -0
- package/dist/jobs/rebuild.d.ts.map +1 -0
- package/dist/jobs/rebuild.js +178 -0
- package/dist/jobs/rebuild.js.map +1 -0
- package/dist/jobs/server-worker.d.ts +16 -0
- package/dist/jobs/server-worker.d.ts.map +1 -0
- package/dist/jobs/server-worker.js +262 -0
- package/dist/jobs/server-worker.js.map +1 -0
- package/dist/jobs/store-adapter.d.ts +3 -0
- package/dist/jobs/store-adapter.d.ts.map +1 -0
- package/dist/jobs/store-adapter.js +37 -0
- package/dist/jobs/store-adapter.js.map +1 -0
- package/dist/jobs/types.d.ts +138 -0
- package/dist/jobs/types.d.ts.map +1 -0
- package/dist/jobs/types.js +2 -0
- package/dist/jobs/types.js.map +1 -0
- package/dist/jobs/worker.d.ts +27 -0
- package/dist/jobs/worker.d.ts.map +1 -0
- package/dist/jobs/worker.js +90 -0
- package/dist/jobs/worker.js.map +1 -0
- package/dist/media-url.d.ts +7 -0
- package/dist/media-url.d.ts.map +1 -0
- package/dist/media-url.js +15 -0
- package/dist/media-url.js.map +1 -0
- package/dist/native-artifact-reassembly.d.ts +25 -0
- package/dist/native-artifact-reassembly.d.ts.map +1 -0
- package/dist/native-artifact-reassembly.js +262 -0
- package/dist/native-artifact-reassembly.js.map +1 -0
- package/dist/native-artifact.d.ts +88 -0
- package/dist/native-artifact.d.ts.map +1 -0
- package/dist/native-artifact.js +86 -0
- package/dist/native-artifact.js.map +1 -0
- package/dist/schema/index.d.ts +7111 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +469 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/migrations.d.ts +10 -0
- package/dist/schema/migrations.d.ts.map +1 -0
- package/dist/schema/migrations.js +333 -0
- package/dist/schema/migrations.js.map +1 -0
- package/dist/search/fusion.d.ts +2 -0
- package/dist/search/fusion.d.ts.map +1 -0
- package/dist/search/fusion.js +2 -0
- package/dist/search/fusion.js.map +1 -0
- package/dist/search/mode.d.ts +4 -0
- package/dist/search/mode.d.ts.map +1 -0
- package/dist/search/mode.js +13 -0
- package/dist/search/mode.js.map +1 -0
- package/dist/search/postgres-fts.d.ts +2 -0
- package/dist/search/postgres-fts.d.ts.map +1 -0
- package/dist/search/postgres-fts.js +2 -0
- package/dist/search/postgres-fts.js.map +1 -0
- package/dist/server/brand-context.d.ts +15 -0
- package/dist/server/brand-context.d.ts.map +1 -0
- package/dist/server/brand-context.js +149 -0
- package/dist/server/brand-context.js.map +1 -0
- package/dist/server/context.d.ts +77 -0
- package/dist/server/context.d.ts.map +1 -0
- package/dist/server/context.js +63 -0
- package/dist/server/context.js.map +1 -0
- package/dist/server/enrichment.d.ts +24 -0
- package/dist/server/enrichment.d.ts.map +1 -0
- package/dist/server/enrichment.js +138 -0
- package/dist/server/enrichment.js.map +1 -0
- package/dist/server/generation-artifact-access.d.ts +21 -0
- package/dist/server/generation-artifact-access.d.ts.map +1 -0
- package/dist/server/generation-artifact-access.js +113 -0
- package/dist/server/generation-artifact-access.js.map +1 -0
- package/dist/server/generation-context.d.ts +220 -0
- package/dist/server/generation-context.d.ts.map +1 -0
- package/dist/server/generation-context.js +432 -0
- package/dist/server/generation-context.js.map +1 -0
- package/dist/server/index.d.ts +27 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +115 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/isolated-a2a.d.ts +175 -0
- package/dist/server/isolated-a2a.d.ts.map +1 -0
- package/dist/server/isolated-a2a.js +312 -0
- package/dist/server/isolated-a2a.js.map +1 -0
- package/dist/server/media.d.ts +15 -0
- package/dist/server/media.d.ts.map +1 -0
- package/dist/server/media.js +108 -0
- package/dist/server/media.js.map +1 -0
- package/dist/server/native-resource-capture.d.ts +78 -0
- package/dist/server/native-resource-capture.d.ts.map +1 -0
- package/dist/server/native-resource-capture.js +94 -0
- package/dist/server/native-resource-capture.js.map +1 -0
- package/dist/server/prompt-provider.d.ts +5 -0
- package/dist/server/prompt-provider.d.ts.map +1 -0
- package/dist/server/prompt-provider.js +59 -0
- package/dist/server/prompt-provider.js.map +1 -0
- package/dist/server/public-serialization.d.ts +160 -0
- package/dist/server/public-serialization.d.ts.map +1 -0
- package/dist/server/public-serialization.js +108 -0
- package/dist/server/public-serialization.js.map +1 -0
- package/dist/server/resource-paths.d.ts +3 -0
- package/dist/server/resource-paths.d.ts.map +1 -0
- package/dist/server/resource-paths.js +5 -0
- package/dist/server/resource-paths.js.map +1 -0
- package/dist/server/retrieval.d.ts +83 -0
- package/dist/server/retrieval.d.ts.map +1 -0
- package/dist/server/retrieval.js +445 -0
- package/dist/server/retrieval.js.map +1 -0
- package/dist/server/safe-native-preview.d.ts +15 -0
- package/dist/server/safe-native-preview.d.ts.map +1 -0
- package/dist/server/safe-native-preview.js +148 -0
- package/dist/server/safe-native-preview.js.map +1 -0
- package/dist/store/brand.d.ts +47 -0
- package/dist/store/brand.d.ts.map +1 -0
- package/dist/store/brand.js +346 -0
- package/dist/store/brand.js.map +1 -0
- package/dist/store/content.d.ts +134 -0
- package/dist/store/content.d.ts.map +1 -0
- package/dist/store/content.js +1677 -0
- package/dist/store/content.js.map +1 -0
- package/dist/store/contexts.d.ts +216 -0
- package/dist/store/contexts.d.ts.map +1 -0
- package/dist/store/contexts.js +1346 -0
- package/dist/store/contexts.js.map +1 -0
- package/dist/store/generation.d.ts +28 -0
- package/dist/store/generation.d.ts.map +1 -0
- package/dist/store/generation.js +140 -0
- package/dist/store/generation.js.map +1 -0
- package/dist/store/helpers.d.ts +12 -0
- package/dist/store/helpers.d.ts.map +1 -0
- package/dist/store/helpers.js +41 -0
- package/dist/store/helpers.js.map +1 -0
- package/dist/store/index.d.ts +11 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +11 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/inference.d.ts +57 -0
- package/dist/store/inference.d.ts.map +1 -0
- package/dist/store/inference.js +279 -0
- package/dist/store/inference.js.map +1 -0
- package/dist/store/jobs.d.ts +80 -0
- package/dist/store/jobs.d.ts.map +1 -0
- package/dist/store/jobs.js +428 -0
- package/dist/store/jobs.js.map +1 -0
- package/dist/store/native-artifact-summary.d.ts +7 -0
- package/dist/store/native-artifact-summary.d.ts.map +1 -0
- package/dist/store/native-artifact-summary.js +10 -0
- package/dist/store/native-artifact-summary.js.map +1 -0
- package/dist/store/packs.d.ts +36 -0
- package/dist/store/packs.d.ts.map +1 -0
- package/dist/store/packs.js +438 -0
- package/dist/store/packs.js.map +1 -0
- package/dist/store/purge.d.ts +16 -0
- package/dist/store/purge.d.ts.map +1 -0
- package/dist/store/purge.js +354 -0
- package/dist/store/purge.js.map +1 -0
- package/dist/store/sources.d.ts +76 -0
- package/dist/store/sources.d.ts.map +1 -0
- package/dist/store/sources.js +525 -0
- package/dist/store/sources.js.map +1 -0
- package/dist/store/suggestions.d.ts +29 -0
- package/dist/store/suggestions.d.ts.map +1 -0
- package/dist/store/suggestions.js +270 -0
- package/dist/store/suggestions.js.map +1 -0
- package/dist/types.d.ts +578 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/dist/untrusted-reference.d.ts +8 -0
- package/dist/untrusted-reference.d.ts.map +1 -0
- package/dist/untrusted-reference.js +19 -0
- package/dist/untrusted-reference.js.map +1 -0
- package/dist/vector/index.d.ts +2 -0
- package/dist/vector/index.d.ts.map +1 -0
- package/dist/vector/index.js +2 -0
- package/dist/vector/index.js.map +1 -0
- package/dist/vector/pgvector.d.ts +2 -0
- package/dist/vector/pgvector.d.ts.map +1 -0
- package/dist/vector/pgvector.js +2 -0
- package/dist/vector/pgvector.js.map +1 -0
- package/docs/evaluation.md +92 -0
- package/docs/llms-full.txt +168 -0
- package/docs/llms.txt +76 -0
- package/docs/skills/creative-context/SKILL.md +90 -0
- package/package.json +100 -0
- package/src/actions/agent-input-schemas.spec.ts +46 -0
- package/src/actions/confirm-canonical-logo.ts +20 -0
- package/src/actions/continue-context-import.ts +15 -0
- package/src/actions/creative-context-a2a.spec.ts +162 -0
- package/src/actions/creative-context-a2a.ts +88 -0
- package/src/actions/get-brand-profile.ts +16 -0
- package/src/actions/get-context-import-status.ts +13 -0
- package/src/actions/get-context-item.ts +1 -0
- package/src/actions/get-context-pack.ts +14 -0
- package/src/actions/get-creative-context.spec.ts +435 -0
- package/src/actions/get-creative-context.ts +197 -0
- package/src/actions/get-google-picker-session.spec.ts +62 -0
- package/src/actions/get-google-picker-session.ts +45 -0
- package/src/actions/index.ts +75 -0
- package/src/actions/infer-brand-dna.ts +15 -0
- package/src/actions/job-public-serialization.spec.ts +143 -0
- package/src/actions/list-canonical-logo-candidates.ts +121 -0
- package/src/actions/list-context-connections.spec.ts +23 -0
- package/src/actions/list-context-connections.ts +51 -0
- package/src/actions/list-context-memberships.ts +56 -0
- package/src/actions/list-context-packs.ts +17 -0
- package/src/actions/list-context-sources.ts +21 -0
- package/src/actions/list-context-suggestions.ts +30 -0
- package/src/actions/list-creative-contexts.spec.ts +38 -0
- package/src/actions/list-creative-contexts.ts +32 -0
- package/src/actions/manage-brand-profile.ts +58 -0
- package/src/actions/manage-context-membership.ts +80 -0
- package/src/actions/manage-context-pack.ts +81 -0
- package/src/actions/manage-context-source.ts +151 -0
- package/src/actions/manage-creative-context.ts +73 -0
- package/src/actions/manage-layout-template.ts +51 -0
- package/src/actions/preview-context-import.spec.ts +64 -0
- package/src/actions/preview-context-import.ts +61 -0
- package/src/actions/process-context-purge.ts +28 -0
- package/src/actions/propose-brand-dna.ts +22 -0
- package/src/actions/propose-canonical-logo.ts +19 -0
- package/src/actions/publish-brand-dna.ts +25 -0
- package/src/actions/recommend-context-roots.ts +24 -0
- package/src/actions/record-context-feedback.ts +16 -0
- package/src/actions/review-context-items.ts +69 -0
- package/src/actions/run-embedding-bakeoff.ts +90 -0
- package/src/actions/search-creative-context.ts +48 -0
- package/src/actions/set-brand-dna.ts +22 -0
- package/src/actions/start-context-enrichment.ts +52 -0
- package/src/actions/start-context-import.ts +38 -0
- package/src/client/CreativeContextChip.spec.ts +59 -0
- package/src/client/CreativeContextChip.tsx +243 -0
- package/src/client/CreativeContextPanel.tsx +3600 -0
- package/src/client/CreativeContextSettingsLink.tsx +30 -0
- package/src/client/CreativeContextShareTab.spec.ts +70 -0
- package/src/client/CreativeContextShareTab.tsx +666 -0
- package/src/client/actions.spec.ts +177 -0
- package/src/client/actions.ts +1024 -0
- package/src/client/agent-tab.tsx +56 -0
- package/src/client/application-state.spec.ts +46 -0
- package/src/client/application-state.ts +158 -0
- package/src/client/google-slides-picker.spec.ts +36 -0
- package/src/client/google-slides-picker.ts +146 -0
- package/src/client/index.ts +102 -0
- package/src/client/messages.ts +1370 -0
- package/src/client/source-config.spec.ts +166 -0
- package/src/connectors/connectors.spec.ts +778 -0
- package/src/connectors/context.ts +83 -0
- package/src/connectors/document-parser.ts +5 -0
- package/src/connectors/figma-native.ts +1192 -0
- package/src/connectors/figma-native.visual.spec.ts +221 -0
- package/src/connectors/figma.spec.ts +812 -0
- package/src/connectors/figma.ts +257 -0
- package/src/connectors/fixtures/google-slides-native.json +353 -0
- package/src/connectors/google-slides-native.spec.ts +804 -0
- package/src/connectors/google-slides-native.ts +1656 -0
- package/src/connectors/google-slides-native.visual.spec.ts +119 -0
- package/src/connectors/google-slides.ts +1171 -0
- package/src/connectors/index.ts +112 -0
- package/src/connectors/manual.ts +132 -0
- package/src/connectors/normalize.spec.ts +144 -0
- package/src/connectors/normalize.ts +475 -0
- package/src/connectors/notion.ts +529 -0
- package/src/connectors/private-artifacts.spec.ts +95 -0
- package/src/connectors/private-artifacts.ts +248 -0
- package/src/connectors/provider-response.ts +144 -0
- package/src/connectors/recommendations.ts +237 -0
- package/src/connectors/registry.ts +48 -0
- package/src/connectors/rendered-design.spec.ts +171 -0
- package/src/connectors/rendered-design.ts +495 -0
- package/src/connectors/rendered-page.spec.ts +388 -0
- package/src/connectors/rendered-page.ts +1387 -0
- package/src/connectors/smart-defaults.spec.ts +135 -0
- package/src/connectors/smart-defaults.ts +87 -0
- package/src/connectors/types.ts +113 -0
- package/src/connectors/upload.spec.ts +175 -0
- package/src/connectors/upload.ts +548 -0
- package/src/connectors/website.spec.ts +465 -0
- package/src/connectors/website.ts +777 -0
- package/src/embeddings/index.ts +2 -0
- package/src/embeddings/providers.spec.ts +73 -0
- package/src/embeddings/providers.ts +1 -0
- package/src/embeddings/types.ts +6 -0
- package/src/eval/acceptance-fixtures.ts +97 -0
- package/src/eval/acceptance.spec.ts +92 -0
- package/src/eval/acceptance.ts +371 -0
- package/src/eval/connectors-golden.spec.ts +421 -0
- package/src/eval/eval.spec.ts +166 -0
- package/src/eval/fixtures/README.md +17 -0
- package/src/eval/fixtures/figma-brand-system-v1.json +140 -0
- package/src/eval/fixtures/figma-brand-system-v2.json +127 -0
- package/src/eval/fixtures/launch-hero.png +0 -0
- package/src/eval/fixtures/launch-system-v1.pptx +0 -0
- package/src/eval/fixtures/launch-system-v2.pptx +0 -0
- package/src/eval/fixtures/notion-brand-root.json +218 -0
- package/src/eval/fixtures/pricing-hero.png +0 -0
- package/src/eval/fixtures/rendered-brand-page.html +76 -0
- package/src/eval/fixtures.spec.ts +43 -0
- package/src/eval/fixtures.ts +136 -0
- package/src/eval/index.ts +291 -0
- package/src/eval/library-flow.acceptance.spec.tsx +145 -0
- package/src/index.ts +4 -0
- package/src/jobs/background-worker.ts +423 -0
- package/src/jobs/import-runner.spec.ts +582 -0
- package/src/jobs/import-runner.ts +791 -0
- package/src/jobs/index.ts +40 -0
- package/src/jobs/progress.ts +37 -0
- package/src/jobs/rebuild.ts +245 -0
- package/src/jobs/server-worker.spec.ts +181 -0
- package/src/jobs/server-worker.ts +354 -0
- package/src/jobs/store-adapter.ts +52 -0
- package/src/jobs/types.ts +147 -0
- package/src/jobs/worker.ts +152 -0
- package/src/manifest.spec.ts +26 -0
- package/src/media-url.ts +17 -0
- package/src/native-artifact-reassembly.spec.ts +386 -0
- package/src/native-artifact-reassembly.ts +406 -0
- package/src/native-artifact.ts +102 -0
- package/src/schema/index.ts +527 -0
- package/src/schema/migrations.spec.ts +41 -0
- package/src/schema/migrations.ts +338 -0
- package/src/search/fusion.spec.ts +21 -0
- package/src/search/fusion.ts +1 -0
- package/src/search/mode.spec.ts +28 -0
- package/src/search/mode.ts +21 -0
- package/src/search/postgres-fts.spec.ts +63 -0
- package/src/search/postgres-fts.ts +1 -0
- package/src/server/brand-context.spec.ts +43 -0
- package/src/server/brand-context.ts +160 -0
- package/src/server/context.ts +162 -0
- package/src/server/enrichment.spec.ts +94 -0
- package/src/server/enrichment.ts +166 -0
- package/src/server/generation-artifact-access.spec.ts +104 -0
- package/src/server/generation-artifact-access.ts +217 -0
- package/src/server/generation-context-a2a.spec.ts +282 -0
- package/src/server/generation-context-off.spec.ts +213 -0
- package/src/server/generation-context.spec.ts +124 -0
- package/src/server/generation-context.ts +639 -0
- package/src/server/index.spec.ts +30 -0
- package/src/server/index.ts +163 -0
- package/src/server/isolated-a2a.spec.ts +184 -0
- package/src/server/isolated-a2a.ts +416 -0
- package/src/server/media.spec.ts +120 -0
- package/src/server/media.ts +136 -0
- package/src/server/native-resource-capture.spec.ts +121 -0
- package/src/server/native-resource-capture.ts +198 -0
- package/src/server/prompt-provider.spec.ts +78 -0
- package/src/server/prompt-provider.ts +73 -0
- package/src/server/public-serialization.spec.ts +150 -0
- package/src/server/public-serialization.ts +130 -0
- package/src/server/resource-paths.spec.ts +9 -0
- package/src/server/resource-paths.ts +5 -0
- package/src/server/retrieval-vector.spec.ts +180 -0
- package/src/server/retrieval.ts +590 -0
- package/src/server/safe-native-preview.spec.ts +29 -0
- package/src/server/safe-native-preview.ts +199 -0
- package/src/store/access.integration.spec.ts +3523 -0
- package/src/store/brand.ts +458 -0
- package/src/store/content.ts +2317 -0
- package/src/store/contexts.ts +1840 -0
- package/src/store/generation.ts +226 -0
- package/src/store/helpers.ts +53 -0
- package/src/store/index.ts +10 -0
- package/src/store/inference.spec.ts +84 -0
- package/src/store/inference.ts +339 -0
- package/src/store/invariants.spec.ts +119 -0
- package/src/store/jobs.ts +674 -0
- package/src/store/native-artifact-summary.ts +13 -0
- package/src/store/packs.ts +721 -0
- package/src/store/purge.ts +463 -0
- package/src/store/sources.spec.ts +102 -0
- package/src/store/sources.ts +692 -0
- package/src/store/suggestions.ts +361 -0
- package/src/types.ts +671 -0
- package/src/untrusted-reference.spec.ts +22 -0
- package/src/untrusted-reference.ts +24 -0
- package/src/vector/index.ts +1 -0
- package/src/vector/pgvector.spec.ts +115 -0
- package/src/vector/pgvector.ts +1 -0
- package/tsconfig.json +31 -0
|
@@ -0,0 +1,1387 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
isBlockedExtensionUrlWithDns,
|
|
6
|
+
ssrfSafeFetch,
|
|
7
|
+
} from "@agent-native/core/extensions/url-safety";
|
|
8
|
+
import {
|
|
9
|
+
extractStaticWebsiteContext,
|
|
10
|
+
rankColorSamples,
|
|
11
|
+
readBoundedResponseBytes,
|
|
12
|
+
type WebsiteExtraction,
|
|
13
|
+
} from "@agent-native/core/ingestion";
|
|
14
|
+
|
|
15
|
+
import { normalizeWhitespace } from "./normalize.js";
|
|
16
|
+
|
|
17
|
+
export type RenderedPageMethod =
|
|
18
|
+
| "builder-browser"
|
|
19
|
+
| "local-playwright"
|
|
20
|
+
| "attached-chrome"
|
|
21
|
+
| "static-html";
|
|
22
|
+
|
|
23
|
+
const MAX_RENDERED_TEXT_CHARS = 2_000_000;
|
|
24
|
+
const MAX_SCREENSHOT_BYTES = 10 * 1024 * 1024;
|
|
25
|
+
const MAX_STATIC_HTML_BYTES = 5 * 1024 * 1024;
|
|
26
|
+
const MAX_EXTRACTED_ASSETS = 500;
|
|
27
|
+
const MAX_INTERNAL_LINKS = 500;
|
|
28
|
+
const MAX_COLORS = 256;
|
|
29
|
+
const MAX_TYPOGRAPHY = 100;
|
|
30
|
+
const MAX_SPACING = 100;
|
|
31
|
+
const MAX_RADII = 64;
|
|
32
|
+
const MAX_CSS_VARIABLES = 128;
|
|
33
|
+
const MAX_COMPONENT_STYLES = 24;
|
|
34
|
+
const FONT_READY_TIMEOUT_MS = 4_000;
|
|
35
|
+
const MAX_BROWSER_RESOURCE_BYTES = 12 * 1024 * 1024;
|
|
36
|
+
const MAX_BROWSER_RESOURCE_COUNT = 400;
|
|
37
|
+
const MAX_BROWSER_RESOURCE_BYTES_TOTAL = 64 * 1024 * 1024;
|
|
38
|
+
const BROWSER_RESOURCE_TIMEOUT_MS = 15_000;
|
|
39
|
+
const BROWSER_REQUEST_HEADERS = new Set([
|
|
40
|
+
"accept",
|
|
41
|
+
"accept-language",
|
|
42
|
+
"if-modified-since",
|
|
43
|
+
"if-none-match",
|
|
44
|
+
"origin",
|
|
45
|
+
"range",
|
|
46
|
+
"referer",
|
|
47
|
+
"user-agent",
|
|
48
|
+
]);
|
|
49
|
+
const BROWSER_RESPONSE_HEADERS = new Set([
|
|
50
|
+
"connection",
|
|
51
|
+
"content-encoding",
|
|
52
|
+
"content-length",
|
|
53
|
+
"keep-alive",
|
|
54
|
+
"proxy-authenticate",
|
|
55
|
+
"proxy-authorization",
|
|
56
|
+
"set-cookie",
|
|
57
|
+
"set-cookie2",
|
|
58
|
+
"te",
|
|
59
|
+
"trailer",
|
|
60
|
+
"transfer-encoding",
|
|
61
|
+
"upgrade",
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
export interface RenderedPageRequest {
|
|
65
|
+
url: string;
|
|
66
|
+
timeoutMs?: number;
|
|
67
|
+
waitUntil?: "load" | "domcontentloaded" | "networkidle";
|
|
68
|
+
preferHosted?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface RenderedPageResult {
|
|
72
|
+
url: string;
|
|
73
|
+
finalUrl: string;
|
|
74
|
+
title: string;
|
|
75
|
+
text: string;
|
|
76
|
+
method: RenderedPageMethod;
|
|
77
|
+
rendered: boolean;
|
|
78
|
+
warnings: string[];
|
|
79
|
+
extraction: WebsiteExtraction;
|
|
80
|
+
screenshots: Array<{
|
|
81
|
+
viewport: "desktop" | "mobile";
|
|
82
|
+
width: number;
|
|
83
|
+
height: number;
|
|
84
|
+
data: Uint8Array;
|
|
85
|
+
}>;
|
|
86
|
+
confidence: number;
|
|
87
|
+
classification:
|
|
88
|
+
| "homepage"
|
|
89
|
+
| "marketing"
|
|
90
|
+
| "documentation"
|
|
91
|
+
| "content"
|
|
92
|
+
| "unknown";
|
|
93
|
+
diagnostics: string[];
|
|
94
|
+
metadata: Record<string, unknown>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface RenderedPageProvider {
|
|
98
|
+
render(request: RenderedPageRequest): Promise<RenderedPageResult>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface PlaywrightRequestLike {
|
|
102
|
+
url(): string;
|
|
103
|
+
isNavigationRequest(): boolean;
|
|
104
|
+
resourceType(): string;
|
|
105
|
+
method?(): string;
|
|
106
|
+
headers?(): Record<string, string>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface PlaywrightRouteLike {
|
|
110
|
+
request(): PlaywrightRequestLike;
|
|
111
|
+
continue(): Promise<void>;
|
|
112
|
+
abort(errorCode?: string): Promise<void>;
|
|
113
|
+
fulfill(options: {
|
|
114
|
+
status: number;
|
|
115
|
+
headers: Record<string, string>;
|
|
116
|
+
body: Uint8Array;
|
|
117
|
+
}): Promise<void>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface PlaywrightPageLike {
|
|
121
|
+
route(
|
|
122
|
+
pattern: string,
|
|
123
|
+
handler: (route: PlaywrightRouteLike) => Promise<void>,
|
|
124
|
+
): Promise<void>;
|
|
125
|
+
goto(
|
|
126
|
+
url: string,
|
|
127
|
+
options: { timeout: number; waitUntil: string },
|
|
128
|
+
): Promise<unknown>;
|
|
129
|
+
waitForLoadState?(
|
|
130
|
+
state: "load" | "domcontentloaded" | "networkidle",
|
|
131
|
+
options?: { timeout: number },
|
|
132
|
+
): Promise<void>;
|
|
133
|
+
title(): Promise<string>;
|
|
134
|
+
url(): string;
|
|
135
|
+
locator(selector: string): { innerText(): Promise<string> };
|
|
136
|
+
setViewportSize(size: { width: number; height: number }): Promise<void>;
|
|
137
|
+
screenshot(options: { type: "png"; fullPage: boolean }): Promise<Uint8Array>;
|
|
138
|
+
evaluate<T>(callback: string | (() => T)): Promise<T>;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
interface PlaywrightContextLike {
|
|
142
|
+
pages(): PlaywrightPageLike[];
|
|
143
|
+
newPage(): Promise<PlaywrightPageLike>;
|
|
144
|
+
close?(): Promise<void>;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
interface PlaywrightBrowserLike {
|
|
148
|
+
contexts(): PlaywrightContextLike[];
|
|
149
|
+
newContext?(): Promise<PlaywrightContextLike>;
|
|
150
|
+
close(): Promise<void>;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
interface PlaywrightLike {
|
|
154
|
+
chromium: {
|
|
155
|
+
connectOverCDP(endpoint: string): Promise<PlaywrightBrowserLike>;
|
|
156
|
+
launch(options: {
|
|
157
|
+
headless: boolean;
|
|
158
|
+
args?: string[];
|
|
159
|
+
executablePath?: string;
|
|
160
|
+
}): Promise<PlaywrightBrowserLike>;
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface LayeredRenderedPageProviderOptions {
|
|
165
|
+
requestBuilderBrowserConnection?: (input: {
|
|
166
|
+
sessionId: string;
|
|
167
|
+
}) => Promise<Record<string, unknown>>;
|
|
168
|
+
loadPlaywright?: () => Promise<PlaywrightLike | null>;
|
|
169
|
+
requestAttachedBrowserConnection?: (input: {
|
|
170
|
+
sessionId: string;
|
|
171
|
+
url: string;
|
|
172
|
+
}) => Promise<{ wsUrl: string }>;
|
|
173
|
+
staticFetch?: typeof ssrfSafeFetch;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export class LayeredRenderedPageProvider implements RenderedPageProvider {
|
|
177
|
+
readonly #requestBuilderBrowserConnection: (input: {
|
|
178
|
+
sessionId: string;
|
|
179
|
+
}) => Promise<Record<string, unknown>>;
|
|
180
|
+
readonly #loadPlaywright: () => Promise<PlaywrightLike | null>;
|
|
181
|
+
readonly #requestAttachedBrowserConnection?: NonNullable<
|
|
182
|
+
LayeredRenderedPageProviderOptions["requestAttachedBrowserConnection"]
|
|
183
|
+
>;
|
|
184
|
+
readonly #staticFetch: typeof ssrfSafeFetch;
|
|
185
|
+
|
|
186
|
+
constructor(options: LayeredRenderedPageProviderOptions = {}) {
|
|
187
|
+
this.#requestBuilderBrowserConnection =
|
|
188
|
+
options.requestBuilderBrowserConnection ?? defaultBuilderBrowserRequest;
|
|
189
|
+
this.#loadPlaywright = options.loadPlaywright ?? loadOptionalPlaywright;
|
|
190
|
+
this.#requestAttachedBrowserConnection =
|
|
191
|
+
options.requestAttachedBrowserConnection;
|
|
192
|
+
this.#staticFetch = options.staticFetch ?? ssrfSafeFetch;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async render(request: RenderedPageRequest): Promise<RenderedPageResult> {
|
|
196
|
+
await assertPublicBrowserUrl(request.url);
|
|
197
|
+
const warnings: string[] = [];
|
|
198
|
+
const playwright = await this.#loadPlaywright().catch((error) => {
|
|
199
|
+
warnings.push(`Playwright unavailable: ${errorMessage(error)}`);
|
|
200
|
+
return null;
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (request.preferHosted !== false && playwright) {
|
|
204
|
+
try {
|
|
205
|
+
const connection = await this.#requestBuilderBrowserConnection({
|
|
206
|
+
sessionId: `creative-context-${randomUUID()}`,
|
|
207
|
+
});
|
|
208
|
+
const wsUrl =
|
|
209
|
+
typeof connection.wsUrl === "string" ? connection.wsUrl.trim() : "";
|
|
210
|
+
if (!wsUrl) throw new Error("Builder Browser did not return wsUrl.");
|
|
211
|
+
return await renderWithPlaywright(
|
|
212
|
+
playwright,
|
|
213
|
+
request,
|
|
214
|
+
warnings,
|
|
215
|
+
"builder-browser",
|
|
216
|
+
wsUrl,
|
|
217
|
+
);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
warnings.push(`Builder Browser unavailable: ${errorMessage(error)}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (playwright) {
|
|
224
|
+
try {
|
|
225
|
+
return await renderWithPlaywright(
|
|
226
|
+
playwright,
|
|
227
|
+
request,
|
|
228
|
+
warnings,
|
|
229
|
+
"local-playwright",
|
|
230
|
+
);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
warnings.push(`Local Playwright unavailable: ${errorMessage(error)}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (playwright && this.#requestAttachedBrowserConnection) {
|
|
237
|
+
try {
|
|
238
|
+
const connection = await this.#requestAttachedBrowserConnection({
|
|
239
|
+
sessionId: `creative-context-attached-${randomUUID()}`,
|
|
240
|
+
url: request.url,
|
|
241
|
+
});
|
|
242
|
+
if (!connection.wsUrl?.trim()) {
|
|
243
|
+
throw new Error("Approved attached browser did not return wsUrl.");
|
|
244
|
+
}
|
|
245
|
+
return await renderWithPlaywright(
|
|
246
|
+
playwright,
|
|
247
|
+
request,
|
|
248
|
+
warnings,
|
|
249
|
+
"attached-chrome",
|
|
250
|
+
connection.wsUrl,
|
|
251
|
+
);
|
|
252
|
+
} catch (error) {
|
|
253
|
+
warnings.push(`Attached Chrome unavailable: ${errorMessage(error)}`);
|
|
254
|
+
}
|
|
255
|
+
} else {
|
|
256
|
+
warnings.push(
|
|
257
|
+
"Attached Chrome unavailable: no approved browser connection adapter is configured.",
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return renderStatic(request, warnings, this.#staticFetch);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export async function renderWithPlaywright(
|
|
266
|
+
playwright: PlaywrightLike,
|
|
267
|
+
request: RenderedPageRequest,
|
|
268
|
+
warnings: string[],
|
|
269
|
+
method: "builder-browser" | "local-playwright" | "attached-chrome",
|
|
270
|
+
wsUrl?: string,
|
|
271
|
+
): Promise<RenderedPageResult> {
|
|
272
|
+
const browser = wsUrl
|
|
273
|
+
? await playwright.chromium.connectOverCDP(wsUrl)
|
|
274
|
+
: await launchChromium(playwright.chromium);
|
|
275
|
+
let isolatedContext: PlaywrightContextLike | undefined;
|
|
276
|
+
try {
|
|
277
|
+
// Never reuse a connected browser's ambient context: it can carry cookies,
|
|
278
|
+
// extensions, or tabs from another workflow. The safe proxy below is only
|
|
279
|
+
// useful when the page itself is isolated from that state.
|
|
280
|
+
if (!browser.newContext) {
|
|
281
|
+
throw new Error("Browser did not provide isolated context support.");
|
|
282
|
+
}
|
|
283
|
+
isolatedContext = await browser.newContext();
|
|
284
|
+
const page = await isolatedContext.newPage();
|
|
285
|
+
const getFinalNavigationUrl = await installNavigationGuard(
|
|
286
|
+
page,
|
|
287
|
+
request,
|
|
288
|
+
warnings,
|
|
289
|
+
);
|
|
290
|
+
await page.setViewportSize({ width: 1440, height: 900 });
|
|
291
|
+
await page.goto(request.url, {
|
|
292
|
+
timeout: boundedTimeout(request.timeoutMs),
|
|
293
|
+
waitUntil: request.waitUntil ?? "domcontentloaded",
|
|
294
|
+
});
|
|
295
|
+
await page
|
|
296
|
+
.waitForLoadState?.("load", {
|
|
297
|
+
timeout: Math.min(8_000, boundedTimeout(request.timeoutMs)),
|
|
298
|
+
})
|
|
299
|
+
.catch((error) => {
|
|
300
|
+
warnings.push(
|
|
301
|
+
`Browser load stabilization unavailable: ${errorMessage(error)}`,
|
|
302
|
+
);
|
|
303
|
+
});
|
|
304
|
+
// React hydration, CSS-in-JS insertion, and web fonts commonly finish just
|
|
305
|
+
// after `load`. Give those layers a bounded chance to settle, then capture
|
|
306
|
+
// the computed cascade rather than the server HTML.
|
|
307
|
+
await page
|
|
308
|
+
.waitForLoadState?.("networkidle", { timeout: 4_000 })
|
|
309
|
+
.catch((error) => {
|
|
310
|
+
warnings.push(
|
|
311
|
+
`Browser network-idle stabilization unavailable: ${errorMessage(error)}`,
|
|
312
|
+
);
|
|
313
|
+
});
|
|
314
|
+
await waitForFontReadiness(
|
|
315
|
+
page,
|
|
316
|
+
Math.min(FONT_READY_TIMEOUT_MS, boundedTimeout(request.timeoutMs)),
|
|
317
|
+
).catch((error) => {
|
|
318
|
+
warnings.push(
|
|
319
|
+
`Browser font readiness unavailable: ${errorMessage(error)}`,
|
|
320
|
+
);
|
|
321
|
+
});
|
|
322
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
323
|
+
await page.evaluate(dismissConsentOverlays).catch(() => undefined);
|
|
324
|
+
const finalUrl = getFinalNavigationUrl() ?? page.url();
|
|
325
|
+
await assertPublicBrowserUrl(finalUrl);
|
|
326
|
+
const [title, text, desktopScreenshot, extraction] = await Promise.all([
|
|
327
|
+
page.title().catch((error) => {
|
|
328
|
+
warnings.push(
|
|
329
|
+
`Browser title extraction unavailable: ${errorMessage(error)}`,
|
|
330
|
+
);
|
|
331
|
+
return "";
|
|
332
|
+
}),
|
|
333
|
+
page
|
|
334
|
+
.locator("body")
|
|
335
|
+
.innerText()
|
|
336
|
+
.catch((error) => {
|
|
337
|
+
warnings.push(
|
|
338
|
+
`Browser text extraction unavailable: ${errorMessage(error)}`,
|
|
339
|
+
);
|
|
340
|
+
return "";
|
|
341
|
+
}),
|
|
342
|
+
page
|
|
343
|
+
.screenshot({ type: "png", fullPage: false })
|
|
344
|
+
.then(boundedScreenshot)
|
|
345
|
+
.catch((error) => {
|
|
346
|
+
warnings.push(
|
|
347
|
+
`Desktop screenshot unavailable: ${errorMessage(error)}`,
|
|
348
|
+
);
|
|
349
|
+
return undefined;
|
|
350
|
+
}),
|
|
351
|
+
page
|
|
352
|
+
.evaluate<WebsiteExtraction>(browserCaptureExpression())
|
|
353
|
+
.catch((error) => {
|
|
354
|
+
warnings.push(
|
|
355
|
+
`Browser style extraction unavailable: ${errorMessage(error)}`,
|
|
356
|
+
);
|
|
357
|
+
return emptyExtraction();
|
|
358
|
+
}),
|
|
359
|
+
]);
|
|
360
|
+
await page.setViewportSize({ width: 390, height: 844 });
|
|
361
|
+
const mobileScreenshot = await page
|
|
362
|
+
.screenshot({ type: "png", fullPage: false })
|
|
363
|
+
.then(boundedScreenshot)
|
|
364
|
+
.catch((error) => {
|
|
365
|
+
warnings.push(`Mobile screenshot unavailable: ${errorMessage(error)}`);
|
|
366
|
+
return undefined;
|
|
367
|
+
});
|
|
368
|
+
const unboundedText = normalizeWhitespace(text);
|
|
369
|
+
const textTruncated = unboundedText.length > MAX_RENDERED_TEXT_CHARS;
|
|
370
|
+
const normalizedText = unboundedText.slice(0, MAX_RENDERED_TEXT_CHARS);
|
|
371
|
+
const resolvedExtraction = boundWebsiteExtraction({
|
|
372
|
+
...extraction,
|
|
373
|
+
title: normalizeWhitespace(title) || extraction.title,
|
|
374
|
+
text: normalizedText || extraction.text,
|
|
375
|
+
designTokens: {
|
|
376
|
+
...extraction.designTokens,
|
|
377
|
+
colors: rankColorSamples(extraction.designTokens.colors),
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
const diagnostics = [
|
|
381
|
+
...warnings,
|
|
382
|
+
`Captured ${resolvedExtraction.assets.length} assets and ${resolvedExtraction.internalLinks.length} same-origin links.`,
|
|
383
|
+
...(desktopScreenshot && mobileScreenshot
|
|
384
|
+
? []
|
|
385
|
+
: ["One or more viewport screenshots could not be captured."]),
|
|
386
|
+
...(textTruncated
|
|
387
|
+
? [`Body text was truncated at ${MAX_RENDERED_TEXT_CHARS} characters.`]
|
|
388
|
+
: []),
|
|
389
|
+
];
|
|
390
|
+
return {
|
|
391
|
+
url: request.url,
|
|
392
|
+
finalUrl,
|
|
393
|
+
title: normalizeWhitespace(title) || new URL(finalUrl).hostname,
|
|
394
|
+
text: resolvedExtraction.text,
|
|
395
|
+
method,
|
|
396
|
+
rendered: true,
|
|
397
|
+
warnings,
|
|
398
|
+
extraction: resolvedExtraction,
|
|
399
|
+
screenshots: [
|
|
400
|
+
...(desktopScreenshot
|
|
401
|
+
? [
|
|
402
|
+
{
|
|
403
|
+
viewport: "desktop" as const,
|
|
404
|
+
width: 1440,
|
|
405
|
+
height: 900,
|
|
406
|
+
data: desktopScreenshot,
|
|
407
|
+
},
|
|
408
|
+
]
|
|
409
|
+
: []),
|
|
410
|
+
...(mobileScreenshot
|
|
411
|
+
? [
|
|
412
|
+
{
|
|
413
|
+
viewport: "mobile" as const,
|
|
414
|
+
width: 390,
|
|
415
|
+
height: 844,
|
|
416
|
+
data: mobileScreenshot,
|
|
417
|
+
},
|
|
418
|
+
]
|
|
419
|
+
: []),
|
|
420
|
+
],
|
|
421
|
+
confidence: 0.92,
|
|
422
|
+
classification: classifyWebsite(resolvedExtraction, finalUrl),
|
|
423
|
+
diagnostics,
|
|
424
|
+
metadata: {
|
|
425
|
+
browser: method,
|
|
426
|
+
assetCount: resolvedExtraction.assets.length,
|
|
427
|
+
internalLinkCount: resolvedExtraction.internalLinks.length,
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
} finally {
|
|
431
|
+
await isolatedContext?.close?.().catch((error) => {
|
|
432
|
+
warnings.push(
|
|
433
|
+
`Browser context cleanup unavailable: ${errorMessage(error)}`,
|
|
434
|
+
);
|
|
435
|
+
});
|
|
436
|
+
await browser.close().catch(() => undefined);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async function waitForFontReadiness(
|
|
441
|
+
page: PlaywrightPageLike,
|
|
442
|
+
timeoutMs: number,
|
|
443
|
+
): Promise<void> {
|
|
444
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
445
|
+
try {
|
|
446
|
+
await Promise.race([
|
|
447
|
+
page.evaluate(async () => {
|
|
448
|
+
if (document.fonts?.ready) await document.fonts.ready;
|
|
449
|
+
}),
|
|
450
|
+
new Promise<never>((_, reject) => {
|
|
451
|
+
timeout = setTimeout(() => {
|
|
452
|
+
reject(new Error(`font readiness timed out after ${timeoutMs}ms`));
|
|
453
|
+
}, timeoutMs);
|
|
454
|
+
}),
|
|
455
|
+
]);
|
|
456
|
+
} finally {
|
|
457
|
+
if (timeout !== undefined) clearTimeout(timeout);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async function installNavigationGuard(
|
|
462
|
+
page: PlaywrightPageLike,
|
|
463
|
+
renderRequest: RenderedPageRequest,
|
|
464
|
+
warnings: string[],
|
|
465
|
+
): Promise<() => string | undefined> {
|
|
466
|
+
let finalNavigationUrl: string | undefined;
|
|
467
|
+
let resourceCount = 0;
|
|
468
|
+
let resourceBytes = 0;
|
|
469
|
+
let reservedResourceBytes = 0;
|
|
470
|
+
const bodyBudgetWaiters: Array<() => void> = [];
|
|
471
|
+
let resourceLimitWarningAdded = false;
|
|
472
|
+
let blockedResourceWarningAdded = false;
|
|
473
|
+
let failedResourceWarningAdded = false;
|
|
474
|
+
|
|
475
|
+
const reserveBodyBudget = async (): Promise<() => void> => {
|
|
476
|
+
while (
|
|
477
|
+
reservedResourceBytes + MAX_BROWSER_RESOURCE_BYTES >
|
|
478
|
+
MAX_BROWSER_RESOURCE_BYTES_TOTAL
|
|
479
|
+
) {
|
|
480
|
+
await new Promise<void>((resolve) => {
|
|
481
|
+
bodyBudgetWaiters.push(resolve);
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
reservedResourceBytes += MAX_BROWSER_RESOURCE_BYTES;
|
|
485
|
+
let released = false;
|
|
486
|
+
return () => {
|
|
487
|
+
if (released) return;
|
|
488
|
+
released = true;
|
|
489
|
+
reservedResourceBytes -= MAX_BROWSER_RESOURCE_BYTES;
|
|
490
|
+
bodyBudgetWaiters.shift()?.();
|
|
491
|
+
};
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
const addWarning = (value: string): void => {
|
|
495
|
+
if (!warnings.includes(value)) warnings.push(value);
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
await page.route("**/*", async (route) => {
|
|
499
|
+
const browserRequest = route.request();
|
|
500
|
+
let parsed: URL;
|
|
501
|
+
try {
|
|
502
|
+
parsed = new URL(browserRequest.url());
|
|
503
|
+
} catch {
|
|
504
|
+
// coercion-ok: an absent optional package means the next compatible probe should run.
|
|
505
|
+
await route.abort("blockedbyclient");
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (
|
|
509
|
+
parsed.protocol === "about:" ||
|
|
510
|
+
parsed.protocol === "data:" ||
|
|
511
|
+
parsed.protocol === "blob:"
|
|
512
|
+
) {
|
|
513
|
+
await route.continue();
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
517
|
+
await route.abort("blockedbyclient");
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const method = (browserRequest.method?.() ?? "GET").toUpperCase();
|
|
522
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
523
|
+
await route.abort("blockedbyclient");
|
|
524
|
+
if (!resourceLimitWarningAdded) {
|
|
525
|
+
resourceLimitWarningAdded = true;
|
|
526
|
+
addWarning(
|
|
527
|
+
"Browser blocked a non-read-only resource request during extraction.",
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (resourceCount >= MAX_BROWSER_RESOURCE_COUNT) {
|
|
533
|
+
await route.abort("blockedbyclient");
|
|
534
|
+
if (!resourceLimitWarningAdded) {
|
|
535
|
+
resourceLimitWarningAdded = true;
|
|
536
|
+
addWarning(
|
|
537
|
+
`Browser resource budget reached (${MAX_BROWSER_RESOURCE_COUNT} requests).`,
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Reserve the request slot before the first await. Browser route handlers
|
|
544
|
+
// overlap, so incrementing only after the proxy response arrives lets a
|
|
545
|
+
// burst of requests all pass the limit check.
|
|
546
|
+
resourceCount += 1;
|
|
547
|
+
let bodyBudgetRelease: (() => void) | undefined;
|
|
548
|
+
let committedBytes = 0;
|
|
549
|
+
let fulfilled = false;
|
|
550
|
+
|
|
551
|
+
try {
|
|
552
|
+
await assertPublicBrowserUrl(parsed.href);
|
|
553
|
+
const response = await ssrfSafeFetch(
|
|
554
|
+
parsed.href,
|
|
555
|
+
{
|
|
556
|
+
method,
|
|
557
|
+
headers: browserRequestHeaders(browserRequest),
|
|
558
|
+
signal: AbortSignal.timeout(
|
|
559
|
+
Math.min(
|
|
560
|
+
BROWSER_RESOURCE_TIMEOUT_MS,
|
|
561
|
+
boundedTimeout(renderRequest.timeoutMs),
|
|
562
|
+
),
|
|
563
|
+
),
|
|
564
|
+
},
|
|
565
|
+
{ maxRedirects: 5 },
|
|
566
|
+
);
|
|
567
|
+
bodyBudgetRelease = await reserveBodyBudget();
|
|
568
|
+
const body = await readBoundedResponseBytes(
|
|
569
|
+
response,
|
|
570
|
+
MAX_BROWSER_RESOURCE_BYTES,
|
|
571
|
+
);
|
|
572
|
+
bodyBudgetRelease();
|
|
573
|
+
bodyBudgetRelease = undefined;
|
|
574
|
+
if (resourceBytes + body.byteLength > MAX_BROWSER_RESOURCE_BYTES_TOTAL) {
|
|
575
|
+
await route.abort("blockedbyclient");
|
|
576
|
+
if (!resourceLimitWarningAdded) {
|
|
577
|
+
resourceLimitWarningAdded = true;
|
|
578
|
+
addWarning(
|
|
579
|
+
`Browser resource budget reached (${MAX_BROWSER_RESOURCE_BYTES_TOTAL} bytes).`,
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
resourceBytes += body.byteLength;
|
|
585
|
+
committedBytes = body.byteLength;
|
|
586
|
+
if (browserRequest.isNavigationRequest()) {
|
|
587
|
+
finalNavigationUrl = response.url || parsed.href;
|
|
588
|
+
}
|
|
589
|
+
await route.fulfill({
|
|
590
|
+
status: response.status,
|
|
591
|
+
headers: browserResponseHeaders(response),
|
|
592
|
+
body: Buffer.from(body),
|
|
593
|
+
});
|
|
594
|
+
fulfilled = true;
|
|
595
|
+
} catch (error) {
|
|
596
|
+
if (committedBytes > 0) {
|
|
597
|
+
resourceBytes -= committedBytes;
|
|
598
|
+
committedBytes = 0;
|
|
599
|
+
}
|
|
600
|
+
await route.abort("blockedbyclient");
|
|
601
|
+
if (isSsrfError(error)) {
|
|
602
|
+
if (!blockedResourceWarningAdded) {
|
|
603
|
+
blockedResourceWarningAdded = true;
|
|
604
|
+
addWarning(
|
|
605
|
+
"Some browser resources were blocked by the SSRF safety policy.",
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
} else if (!failedResourceWarningAdded) {
|
|
609
|
+
failedResourceWarningAdded = true;
|
|
610
|
+
addWarning(
|
|
611
|
+
"Some browser resources could not be fetched through the safe network proxy.",
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
} finally {
|
|
615
|
+
bodyBudgetRelease?.();
|
|
616
|
+
if (!fulfilled) resourceCount -= 1;
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
return () => finalNavigationUrl;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function browserRequestHeaders(
|
|
623
|
+
request: PlaywrightRequestLike,
|
|
624
|
+
): Record<string, string> {
|
|
625
|
+
const source = request.headers?.() ?? {};
|
|
626
|
+
return Object.fromEntries(
|
|
627
|
+
Object.entries(source).filter(([name, value]) => {
|
|
628
|
+
return BROWSER_REQUEST_HEADERS.has(name.toLowerCase()) && Boolean(value);
|
|
629
|
+
}),
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function browserResponseHeaders(response: Response): Record<string, string> {
|
|
634
|
+
const headers: Record<string, string> = {};
|
|
635
|
+
response.headers.forEach((value, name) => {
|
|
636
|
+
if (!BROWSER_RESPONSE_HEADERS.has(name.toLowerCase())) {
|
|
637
|
+
headers[name] = value;
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
return headers;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function isSsrfError(error: unknown): boolean {
|
|
644
|
+
return /ssrf blocked|private\/internal|connect blocked/i.test(
|
|
645
|
+
errorMessage(error),
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const SYSTEM_CHROME_EXECUTABLES = [
|
|
650
|
+
"/usr/bin/google-chrome-stable",
|
|
651
|
+
"/usr/bin/google-chrome",
|
|
652
|
+
"/usr/bin/chromium",
|
|
653
|
+
"/usr/bin/chromium-browser",
|
|
654
|
+
];
|
|
655
|
+
|
|
656
|
+
async function launchChromium(
|
|
657
|
+
chromium: PlaywrightLike["chromium"],
|
|
658
|
+
): Promise<PlaywrightBrowserLike> {
|
|
659
|
+
const launchOptions = {
|
|
660
|
+
headless: true,
|
|
661
|
+
args: ["--no-sandbox", "--disable-dev-shm-usage"],
|
|
662
|
+
};
|
|
663
|
+
let missingBrowserError: unknown;
|
|
664
|
+
try {
|
|
665
|
+
return await chromium.launch(launchOptions);
|
|
666
|
+
} catch (error) {
|
|
667
|
+
if (!isMissingBrowserError(error)) throw error;
|
|
668
|
+
missingBrowserError = error;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const serverlessChromium = await loadOptionalServerlessChromium();
|
|
672
|
+
if (serverlessChromium) {
|
|
673
|
+
try {
|
|
674
|
+
const executablePath = await serverlessChromium.executablePath();
|
|
675
|
+
if (executablePath) {
|
|
676
|
+
return await chromium.launch({
|
|
677
|
+
...launchOptions,
|
|
678
|
+
args: [...launchOptions.args, ...(serverlessChromium.args ?? [])],
|
|
679
|
+
executablePath,
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
} catch (error) {
|
|
683
|
+
if (!isMissingBrowserError(error)) throw error;
|
|
684
|
+
missingBrowserError = error;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
for (const executablePath of SYSTEM_CHROME_EXECUTABLES) {
|
|
689
|
+
if (!existsSync(executablePath)) continue;
|
|
690
|
+
try {
|
|
691
|
+
return await chromium.launch({ ...launchOptions, executablePath });
|
|
692
|
+
} catch {
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
if (missingBrowserError) {
|
|
697
|
+
throw missingBrowserError;
|
|
698
|
+
}
|
|
699
|
+
throw new Error(
|
|
700
|
+
"No Chromium executable is available for browser extraction.",
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
interface ServerlessChromiumLike {
|
|
705
|
+
args?: string[];
|
|
706
|
+
executablePath(): Promise<string>;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
async function loadOptionalServerlessChromium(): Promise<ServerlessChromiumLike | null> {
|
|
710
|
+
const specifier = "@sparticuz/chromium";
|
|
711
|
+
try {
|
|
712
|
+
const module = (await import(/* @vite-ignore */ specifier)) as unknown as {
|
|
713
|
+
default?: Partial<ServerlessChromiumLike>;
|
|
714
|
+
} & Partial<ServerlessChromiumLike>;
|
|
715
|
+
const chromium = module.default ?? module;
|
|
716
|
+
return typeof chromium.executablePath === "function"
|
|
717
|
+
? (chromium as ServerlessChromiumLike)
|
|
718
|
+
: null;
|
|
719
|
+
} catch {
|
|
720
|
+
// coercion-ok: this optional capability is absent in non-serverless installs.
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/*
|
|
726
|
+
* Kept separate from Playwright loading so a deployment can omit the large
|
|
727
|
+
* serverless Chromium package and still use Builder Browser or system Chrome.
|
|
728
|
+
*/
|
|
729
|
+
async function loadOptionalPlaywright(): Promise<PlaywrightLike | null> {
|
|
730
|
+
for (const specifier of [
|
|
731
|
+
"playwright",
|
|
732
|
+
"@playwright/test",
|
|
733
|
+
"playwright-core",
|
|
734
|
+
]) {
|
|
735
|
+
try {
|
|
736
|
+
const module = (await import(
|
|
737
|
+
/* @vite-ignore */ specifier
|
|
738
|
+
)) as unknown as {
|
|
739
|
+
default?: Partial<PlaywrightLike>;
|
|
740
|
+
} & Partial<PlaywrightLike>;
|
|
741
|
+
for (const candidate of [module.default, module]) {
|
|
742
|
+
if (typeof candidate?.chromium?.launch === "function") {
|
|
743
|
+
return candidate as PlaywrightLike;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
// coercion-ok: an absent optional package means the next compatible probe should run.
|
|
747
|
+
} catch {
|
|
748
|
+
// Try the next compatible browser package before falling back to HTML.
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// coercion-ok: browser packages are optional in non-Node runtimes.
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
async function defaultBuilderBrowserRequest(input: {
|
|
757
|
+
sessionId: string;
|
|
758
|
+
}): Promise<Record<string, unknown>> {
|
|
759
|
+
const server = (await import("@agent-native/core/server")) as unknown as {
|
|
760
|
+
requestBuilderBrowserConnection?: (value: {
|
|
761
|
+
sessionId: string;
|
|
762
|
+
}) => Promise<Record<string, unknown>>;
|
|
763
|
+
};
|
|
764
|
+
if (!server.requestBuilderBrowserConnection) {
|
|
765
|
+
throw new Error(
|
|
766
|
+
"@agent-native/core/server does not export requestBuilderBrowserConnection.",
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
return server.requestBuilderBrowserConnection(input);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function isMissingBrowserError(error: unknown): boolean {
|
|
773
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
774
|
+
return /Executable doesn't exist|playwright install|browser.*not found|chromium.*not found/i.test(
|
|
775
|
+
message,
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/** Close common consent banners without accepting tracking or changing page data. */
|
|
780
|
+
function dismissConsentOverlays(): void {
|
|
781
|
+
const selectors = [
|
|
782
|
+
'[aria-label*="reject" i]',
|
|
783
|
+
'[aria-label*="decline" i]',
|
|
784
|
+
'[aria-label*="close" i]',
|
|
785
|
+
'[data-testid*="reject" i]',
|
|
786
|
+
'[data-testid*="decline" i]',
|
|
787
|
+
'[data-testid*="close" i]',
|
|
788
|
+
'button[id*="reject" i]',
|
|
789
|
+
'button[id*="decline" i]',
|
|
790
|
+
'button[class*="reject" i]',
|
|
791
|
+
'button[class*="decline" i]',
|
|
792
|
+
];
|
|
793
|
+
for (const selector of selectors) {
|
|
794
|
+
const element = document.querySelector<HTMLElement>(selector);
|
|
795
|
+
if (!element) continue;
|
|
796
|
+
const rect = element.getBoundingClientRect();
|
|
797
|
+
const style = getComputedStyle(element);
|
|
798
|
+
if (
|
|
799
|
+
rect.width > 0 &&
|
|
800
|
+
rect.height > 0 &&
|
|
801
|
+
style.visibility !== "hidden" &&
|
|
802
|
+
style.display !== "none"
|
|
803
|
+
) {
|
|
804
|
+
element.click();
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
async function renderStatic(
|
|
811
|
+
request: RenderedPageRequest,
|
|
812
|
+
warnings: string[],
|
|
813
|
+
fetcher: typeof ssrfSafeFetch,
|
|
814
|
+
): Promise<RenderedPageResult> {
|
|
815
|
+
const response = await fetcher(
|
|
816
|
+
request.url,
|
|
817
|
+
{
|
|
818
|
+
headers: {
|
|
819
|
+
Accept: "text/html,application/xhtml+xml,text/plain;q=0.9,*/*;q=0.1",
|
|
820
|
+
},
|
|
821
|
+
signal: AbortSignal.timeout(boundedTimeout(request.timeoutMs)),
|
|
822
|
+
},
|
|
823
|
+
{ maxRedirects: 5 },
|
|
824
|
+
);
|
|
825
|
+
if (!response.ok) {
|
|
826
|
+
throw new Error(`Static page fetch failed (${response.status}).`);
|
|
827
|
+
}
|
|
828
|
+
const html = new TextDecoder().decode(
|
|
829
|
+
await readBoundedResponseBytes(response, MAX_STATIC_HTML_BYTES),
|
|
830
|
+
);
|
|
831
|
+
const finalUrl = response.url || request.url;
|
|
832
|
+
const extraction = extractStaticWebsiteContext(html, finalUrl);
|
|
833
|
+
const title = extraction.title || new URL(finalUrl).hostname;
|
|
834
|
+
warnings.push(
|
|
835
|
+
"Used the SSRF-safe static HTML fallback; client-rendered content may be missing.",
|
|
836
|
+
);
|
|
837
|
+
return {
|
|
838
|
+
url: request.url,
|
|
839
|
+
finalUrl,
|
|
840
|
+
title,
|
|
841
|
+
text: extraction.text,
|
|
842
|
+
method: "static-html",
|
|
843
|
+
rendered: false,
|
|
844
|
+
warnings,
|
|
845
|
+
extraction: { ...extraction, title },
|
|
846
|
+
screenshots: [],
|
|
847
|
+
confidence: 0.45,
|
|
848
|
+
classification: classifyWebsite(extraction, finalUrl),
|
|
849
|
+
diagnostics: [
|
|
850
|
+
...warnings,
|
|
851
|
+
"Static extraction cannot verify client-rendered layout or viewport behavior.",
|
|
852
|
+
],
|
|
853
|
+
metadata: {
|
|
854
|
+
contentType: response.headers.get("content-type"),
|
|
855
|
+
etag: response.headers.get("etag"),
|
|
856
|
+
lastModified: response.headers.get("last-modified"),
|
|
857
|
+
staticFallback: true,
|
|
858
|
+
assetCount: extraction.assets.length,
|
|
859
|
+
internalLinkCount: extraction.internalLinks.length,
|
|
860
|
+
},
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
async function assertPublicBrowserUrl(value: string): Promise<void> {
|
|
865
|
+
let parsed: URL;
|
|
866
|
+
try {
|
|
867
|
+
parsed = new URL(value);
|
|
868
|
+
} catch {
|
|
869
|
+
throw new Error("Website URL must be an absolute http(s) URL.");
|
|
870
|
+
}
|
|
871
|
+
if (!(["http:", "https:"] as string[]).includes(parsed.protocol)) {
|
|
872
|
+
throw new Error("Website URL must use http or https.");
|
|
873
|
+
}
|
|
874
|
+
if (parsed.username || parsed.password) {
|
|
875
|
+
throw new Error("Website URLs cannot contain credentials.");
|
|
876
|
+
}
|
|
877
|
+
if (await isBlockedExtensionUrlWithDns(parsed.href)) {
|
|
878
|
+
throw new Error(
|
|
879
|
+
"SSRF blocked: website resolved to a private/internal host.",
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function captureRenderedWebsiteContext(): WebsiteExtraction {
|
|
885
|
+
const MAX_ASSETS = 500;
|
|
886
|
+
const MAX_LINKS = 500;
|
|
887
|
+
const MAX_COLOR_VALUES = 256;
|
|
888
|
+
const MAX_TYPE_STYLES = 100;
|
|
889
|
+
const MAX_SPACING_VALUES = 100;
|
|
890
|
+
const MAX_RADIUS_VALUES = 64;
|
|
891
|
+
const MAX_SHADOWS = 32;
|
|
892
|
+
const MAX_BACKGROUNDS = 32;
|
|
893
|
+
const MAX_VARIABLES = 128;
|
|
894
|
+
const MAX_TEXT = 2_000_000;
|
|
895
|
+
const MAX_COMPONENT_STYLES = 24;
|
|
896
|
+
const assets = new Map<string, WebsiteExtraction["assets"][number]>();
|
|
897
|
+
const links = new Set<string>();
|
|
898
|
+
const colors: string[] = [];
|
|
899
|
+
const typography = new Map<
|
|
900
|
+
string,
|
|
901
|
+
WebsiteExtraction["designTokens"]["typography"][number]
|
|
902
|
+
>();
|
|
903
|
+
const spacing = new Set<string>();
|
|
904
|
+
const radii = new Set<string>();
|
|
905
|
+
const shadows = new Set<string>();
|
|
906
|
+
const backgrounds = new Set<string>();
|
|
907
|
+
const components: NonNullable<
|
|
908
|
+
WebsiteExtraction["designTokens"]["components"]
|
|
909
|
+
> = [];
|
|
910
|
+
const cssVariables: Record<string, string> = {};
|
|
911
|
+
const semanticColors: NonNullable<
|
|
912
|
+
WebsiteExtraction["designTokens"]["semanticColors"]
|
|
913
|
+
> = {};
|
|
914
|
+
|
|
915
|
+
function isOpaque(value: string): boolean {
|
|
916
|
+
const normalized = value.trim().toLowerCase();
|
|
917
|
+
if (!normalized || normalized === "transparent") return false;
|
|
918
|
+
const functionBody = normalized.match(/^[a-z-]+\((.*)\)$/)?.[1];
|
|
919
|
+
if (!functionBody) return true;
|
|
920
|
+
const alphaValue = functionBody.includes("/")
|
|
921
|
+
? functionBody.split("/").at(-1)?.trim()
|
|
922
|
+
: functionBody.split(",").length === 4
|
|
923
|
+
? functionBody.split(",").at(-1)?.trim()
|
|
924
|
+
: undefined;
|
|
925
|
+
if (!alphaValue) return true;
|
|
926
|
+
const alpha = alphaValue.endsWith("%")
|
|
927
|
+
? Number.parseFloat(alphaValue) / 100
|
|
928
|
+
: Number.parseFloat(alphaValue);
|
|
929
|
+
return Number.isNaN(alpha) || alpha > 0.02;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
function addColor(value: string): void {
|
|
933
|
+
if (colors.length >= MAX_COLOR_VALUES || !isOpaque(value)) return;
|
|
934
|
+
const normalized = value.trim();
|
|
935
|
+
if (!colors.includes(normalized)) colors.push(normalized);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
function visible(element: Element): boolean {
|
|
939
|
+
const style = getComputedStyle(element);
|
|
940
|
+
const rect = element.getBoundingClientRect();
|
|
941
|
+
return (
|
|
942
|
+
rect.width > 0 &&
|
|
943
|
+
rect.height > 0 &&
|
|
944
|
+
style.display !== "none" &&
|
|
945
|
+
style.visibility !== "hidden" &&
|
|
946
|
+
Number(style.opacity || 1) > 0.02
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
function firstVisible(selector: string): Element | undefined {
|
|
951
|
+
return Array.from(document.querySelectorAll(selector)).find(visible);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function opaqueValue(value: string): string | undefined {
|
|
955
|
+
return isOpaque(value) ? value.trim() : undefined;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function recordComputedStyle(
|
|
959
|
+
element: Element,
|
|
960
|
+
role?: NonNullable<
|
|
961
|
+
WebsiteExtraction["designTokens"]["components"]
|
|
962
|
+
>[number]["role"],
|
|
963
|
+
): CSSStyleDeclaration {
|
|
964
|
+
const style = getComputedStyle(element);
|
|
965
|
+
const values = [
|
|
966
|
+
style.color,
|
|
967
|
+
style.backgroundColor,
|
|
968
|
+
style.borderTopColor,
|
|
969
|
+
style.borderRightColor,
|
|
970
|
+
style.borderBottomColor,
|
|
971
|
+
style.borderLeftColor,
|
|
972
|
+
];
|
|
973
|
+
values.forEach(addColor);
|
|
974
|
+
if (style.boxShadow && style.boxShadow !== "none") {
|
|
975
|
+
if (shadows.size < MAX_SHADOWS) shadows.add(style.boxShadow);
|
|
976
|
+
}
|
|
977
|
+
if (style.backgroundImage && style.backgroundImage !== "none") {
|
|
978
|
+
if (backgrounds.size < MAX_BACKGROUNDS) {
|
|
979
|
+
backgrounds.add(style.backgroundImage);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
for (const value of [
|
|
983
|
+
style.marginTop,
|
|
984
|
+
style.marginRight,
|
|
985
|
+
style.marginBottom,
|
|
986
|
+
style.marginLeft,
|
|
987
|
+
style.paddingTop,
|
|
988
|
+
style.paddingRight,
|
|
989
|
+
style.paddingBottom,
|
|
990
|
+
style.paddingLeft,
|
|
991
|
+
style.gap,
|
|
992
|
+
style.rowGap,
|
|
993
|
+
style.columnGap,
|
|
994
|
+
]) {
|
|
995
|
+
if (
|
|
996
|
+
spacing.size < MAX_SPACING_VALUES &&
|
|
997
|
+
value &&
|
|
998
|
+
value !== "0px" &&
|
|
999
|
+
value !== "normal"
|
|
1000
|
+
) {
|
|
1001
|
+
spacing.add(value);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
for (const value of [
|
|
1005
|
+
style.borderTopLeftRadius,
|
|
1006
|
+
style.borderTopRightRadius,
|
|
1007
|
+
style.borderBottomRightRadius,
|
|
1008
|
+
style.borderBottomLeftRadius,
|
|
1009
|
+
]) {
|
|
1010
|
+
if (radii.size < MAX_RADIUS_VALUES && value && value !== "0px") {
|
|
1011
|
+
radii.add(value);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
const type = {
|
|
1015
|
+
family: style.fontFamily,
|
|
1016
|
+
size: style.fontSize,
|
|
1017
|
+
weight: style.fontWeight,
|
|
1018
|
+
lineHeight: style.lineHeight,
|
|
1019
|
+
letterSpacing: style.letterSpacing,
|
|
1020
|
+
};
|
|
1021
|
+
if (typography.size < MAX_TYPE_STYLES && type.family) {
|
|
1022
|
+
typography.set(JSON.stringify(type), type);
|
|
1023
|
+
}
|
|
1024
|
+
if (!role || components.length >= MAX_COMPONENT_STYLES) return style;
|
|
1025
|
+
components.push({
|
|
1026
|
+
role,
|
|
1027
|
+
fontFamily: style.fontFamily,
|
|
1028
|
+
fontSize: style.fontSize,
|
|
1029
|
+
fontWeight: style.fontWeight,
|
|
1030
|
+
lineHeight: style.lineHeight,
|
|
1031
|
+
letterSpacing: style.letterSpacing,
|
|
1032
|
+
color: opaqueValue(style.color),
|
|
1033
|
+
backgroundColor: opaqueValue(style.backgroundColor),
|
|
1034
|
+
backgroundImage:
|
|
1035
|
+
style.backgroundImage !== "none" ? style.backgroundImage : undefined,
|
|
1036
|
+
border:
|
|
1037
|
+
style.borderStyle !== "none" && style.borderWidth !== "0px"
|
|
1038
|
+
? style.border
|
|
1039
|
+
: undefined,
|
|
1040
|
+
borderRadius:
|
|
1041
|
+
style.borderTopLeftRadius !== "0px" ? style.borderRadius : undefined,
|
|
1042
|
+
boxShadow: style.boxShadow !== "none" ? style.boxShadow : undefined,
|
|
1043
|
+
padding: style.padding !== "0px" ? style.padding : undefined,
|
|
1044
|
+
gap: style.gap !== "normal" ? style.gap : undefined,
|
|
1045
|
+
textTransform:
|
|
1046
|
+
style.textTransform !== "none" ? style.textTransform : undefined,
|
|
1047
|
+
});
|
|
1048
|
+
return style;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function styleFor(
|
|
1052
|
+
selector: string,
|
|
1053
|
+
role?: NonNullable<
|
|
1054
|
+
WebsiteExtraction["designTokens"]["components"]
|
|
1055
|
+
>[number]["role"],
|
|
1056
|
+
): CSSStyleDeclaration | undefined {
|
|
1057
|
+
const element = firstVisible(selector);
|
|
1058
|
+
if (!element) return undefined;
|
|
1059
|
+
return recordComputedStyle(element, role);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
function addAsset(
|
|
1063
|
+
raw: string | null | undefined,
|
|
1064
|
+
kind: WebsiteExtraction["assets"][number]["kind"],
|
|
1065
|
+
role?: "logo" | "open-graph",
|
|
1066
|
+
): void {
|
|
1067
|
+
if (!raw || assets.size >= MAX_ASSETS) return;
|
|
1068
|
+
try {
|
|
1069
|
+
const url = new URL(raw, document.baseURI);
|
|
1070
|
+
if (url.protocol === "http:" || url.protocol === "https:") {
|
|
1071
|
+
url.hash = "";
|
|
1072
|
+
assets.set(url.href, {
|
|
1073
|
+
url: url.href,
|
|
1074
|
+
kind,
|
|
1075
|
+
...(role ? { role } : {}),
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
} catch {
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
for (const image of document.querySelectorAll("img")) {
|
|
1083
|
+
if (assets.size >= MAX_ASSETS) break;
|
|
1084
|
+
const identity = `${image.getAttribute("alt") ?? ""} ${image.getAttribute("class") ?? ""} ${image.id}`;
|
|
1085
|
+
addAsset(
|
|
1086
|
+
image.currentSrc || image.src,
|
|
1087
|
+
"image",
|
|
1088
|
+
/logo|wordmark|brandmark/i.test(identity) ? "logo" : undefined,
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
for (const video of document.querySelectorAll("video")) {
|
|
1092
|
+
if (assets.size >= MAX_ASSETS) break;
|
|
1093
|
+
addAsset(video.currentSrc || video.src, "video");
|
|
1094
|
+
}
|
|
1095
|
+
for (const audio of document.querySelectorAll("audio")) {
|
|
1096
|
+
if (assets.size >= MAX_ASSETS) break;
|
|
1097
|
+
addAsset(audio.currentSrc || audio.src, "audio");
|
|
1098
|
+
}
|
|
1099
|
+
for (const script of document.querySelectorAll("script[src]")) {
|
|
1100
|
+
if (assets.size >= MAX_ASSETS) break;
|
|
1101
|
+
addAsset(script.getAttribute("src"), "script");
|
|
1102
|
+
}
|
|
1103
|
+
for (const link of document.querySelectorAll("link[href]")) {
|
|
1104
|
+
if (assets.size >= MAX_ASSETS) break;
|
|
1105
|
+
const rel = link.getAttribute("rel") ?? "";
|
|
1106
|
+
const icon = /icon/i.test(rel);
|
|
1107
|
+
addAsset(
|
|
1108
|
+
link.getAttribute("href"),
|
|
1109
|
+
icon ? "image" : /font|preload/i.test(rel) ? "font" : "stylesheet",
|
|
1110
|
+
icon ? "logo" : undefined,
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
for (const meta of document.querySelectorAll(
|
|
1114
|
+
'meta[property="og:image"],meta[property="og:image:url"],meta[name="twitter:image"]',
|
|
1115
|
+
)) {
|
|
1116
|
+
if (assets.size >= MAX_ASSETS) break;
|
|
1117
|
+
addAsset(meta.getAttribute("content"), "image", "open-graph");
|
|
1118
|
+
}
|
|
1119
|
+
for (const anchor of document.querySelectorAll("a[href]")) {
|
|
1120
|
+
if (links.size >= MAX_LINKS) break;
|
|
1121
|
+
try {
|
|
1122
|
+
const url = new URL(anchor.getAttribute("href") ?? "", document.baseURI);
|
|
1123
|
+
if (url.origin === location.origin) {
|
|
1124
|
+
url.hash = "";
|
|
1125
|
+
links.add(url.href);
|
|
1126
|
+
}
|
|
1127
|
+
} catch {
|
|
1128
|
+
continue;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
const rootStyle = getComputedStyle(document.documentElement);
|
|
1132
|
+
let variableCount = 0;
|
|
1133
|
+
for (const name of rootStyle) {
|
|
1134
|
+
if (name.startsWith("--")) {
|
|
1135
|
+
const value = rootStyle.getPropertyValue(name).trim();
|
|
1136
|
+
if (value) {
|
|
1137
|
+
cssVariables[name] = value;
|
|
1138
|
+
variableCount++;
|
|
1139
|
+
if (variableCount >= MAX_VARIABLES) break;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
const bodyStyle = document.body
|
|
1145
|
+
? recordComputedStyle(document.body)
|
|
1146
|
+
: undefined;
|
|
1147
|
+
const rootBackground = opaqueValue(rootStyle.backgroundColor);
|
|
1148
|
+
const bodyBackground = bodyStyle
|
|
1149
|
+
? opaqueValue(bodyStyle.backgroundColor)
|
|
1150
|
+
: undefined;
|
|
1151
|
+
|
|
1152
|
+
const textStyle = styleFor("p, li, label, body", "body");
|
|
1153
|
+
const buttonStyle = styleFor(
|
|
1154
|
+
'button, [role="button"], input[type="submit"], a[class*="button" i], a[class*="cta" i]',
|
|
1155
|
+
"button",
|
|
1156
|
+
);
|
|
1157
|
+
const linkStyle = styleFor("a[href]", "link");
|
|
1158
|
+
const cardStyle = styleFor(
|
|
1159
|
+
'article, [class*="card" i], [class*="panel" i], [class*="surface" i], section',
|
|
1160
|
+
"card",
|
|
1161
|
+
);
|
|
1162
|
+
styleFor("input, textarea, select", "input");
|
|
1163
|
+
styleFor("nav, header", "nav");
|
|
1164
|
+
styleFor('main, [class*="hero" i]', "hero");
|
|
1165
|
+
|
|
1166
|
+
function setSemantic(
|
|
1167
|
+
role: keyof NonNullable<
|
|
1168
|
+
WebsiteExtraction["designTokens"]["semanticColors"]
|
|
1169
|
+
>,
|
|
1170
|
+
value: string | undefined,
|
|
1171
|
+
): void {
|
|
1172
|
+
if (value) semanticColors[role] = value;
|
|
1173
|
+
}
|
|
1174
|
+
setSemantic("background", bodyBackground ?? rootBackground);
|
|
1175
|
+
setSemantic(
|
|
1176
|
+
"surface",
|
|
1177
|
+
cardStyle ? opaqueValue(cardStyle.backgroundColor) : undefined,
|
|
1178
|
+
);
|
|
1179
|
+
setSemantic("text", textStyle ? opaqueValue(textStyle.color) : undefined);
|
|
1180
|
+
const mutedElement = firstVisible("small, figcaption, [class*='muted' i]");
|
|
1181
|
+
setSemantic(
|
|
1182
|
+
"textMuted",
|
|
1183
|
+
mutedElement
|
|
1184
|
+
? opaqueValue(getComputedStyle(mutedElement).color)
|
|
1185
|
+
: undefined,
|
|
1186
|
+
);
|
|
1187
|
+
setSemantic(
|
|
1188
|
+
"accent",
|
|
1189
|
+
linkStyle
|
|
1190
|
+
? opaqueValue(linkStyle.color)
|
|
1191
|
+
: buttonStyle
|
|
1192
|
+
? opaqueValue(buttonStyle.backgroundColor)
|
|
1193
|
+
: undefined,
|
|
1194
|
+
);
|
|
1195
|
+
setSemantic(
|
|
1196
|
+
"primary",
|
|
1197
|
+
buttonStyle
|
|
1198
|
+
? (opaqueValue(buttonStyle.backgroundColor) ??
|
|
1199
|
+
opaqueValue(buttonStyle.color))
|
|
1200
|
+
: colors[0],
|
|
1201
|
+
);
|
|
1202
|
+
setSemantic(
|
|
1203
|
+
"secondary",
|
|
1204
|
+
semanticColors.surface ?? colors[1] ?? semanticColors.background,
|
|
1205
|
+
);
|
|
1206
|
+
|
|
1207
|
+
const layoutElement = firstVisible("main, [role='main'], body > div");
|
|
1208
|
+
const layoutStyle = layoutElement
|
|
1209
|
+
? getComputedStyle(layoutElement)
|
|
1210
|
+
: undefined;
|
|
1211
|
+
const layoutRect = layoutElement?.getBoundingClientRect();
|
|
1212
|
+
const layout = {
|
|
1213
|
+
contentWidth:
|
|
1214
|
+
layoutStyle?.maxWidth && layoutStyle.maxWidth !== "none"
|
|
1215
|
+
? layoutStyle.maxWidth
|
|
1216
|
+
: layoutRect && layoutRect.width > 0
|
|
1217
|
+
? `${Math.round(layoutRect.width)}px`
|
|
1218
|
+
: undefined,
|
|
1219
|
+
pagePadding:
|
|
1220
|
+
bodyStyle?.padding && bodyStyle.padding !== "0px"
|
|
1221
|
+
? bodyStyle.padding
|
|
1222
|
+
: undefined,
|
|
1223
|
+
sectionGap:
|
|
1224
|
+
firstVisible("section, article") &&
|
|
1225
|
+
getComputedStyle(firstVisible("section, article")!).gap !== "normal" &&
|
|
1226
|
+
getComputedStyle(firstVisible("section, article")!).gap !== "0px"
|
|
1227
|
+
? getComputedStyle(firstVisible("section, article")!).gap
|
|
1228
|
+
: undefined,
|
|
1229
|
+
};
|
|
1230
|
+
|
|
1231
|
+
const elements = Array.from(document.querySelectorAll("body *"))
|
|
1232
|
+
.filter(visible)
|
|
1233
|
+
.slice(0, 700);
|
|
1234
|
+
for (const element of elements) {
|
|
1235
|
+
recordComputedStyle(element);
|
|
1236
|
+
}
|
|
1237
|
+
return {
|
|
1238
|
+
title: document.title,
|
|
1239
|
+
text: (document.body?.innerText ?? "").slice(0, MAX_TEXT),
|
|
1240
|
+
assets: [...assets.values()],
|
|
1241
|
+
internalLinks: [...links],
|
|
1242
|
+
designTokens: {
|
|
1243
|
+
colors,
|
|
1244
|
+
typography: [...typography.values()],
|
|
1245
|
+
spacing: [...spacing],
|
|
1246
|
+
radii: [...radii],
|
|
1247
|
+
cssVariables,
|
|
1248
|
+
semanticColors,
|
|
1249
|
+
shadows: [...shadows],
|
|
1250
|
+
backgrounds: [...backgrounds],
|
|
1251
|
+
components,
|
|
1252
|
+
layout,
|
|
1253
|
+
},
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
function browserCaptureExpression(): string {
|
|
1258
|
+
// Bundlers can inject a module-scoped `__name` helper into nested functions.
|
|
1259
|
+
// Playwright serializes only the function body into Chromium, so provide the
|
|
1260
|
+
// tiny identity helper in the browser expression rather than leaking a
|
|
1261
|
+
// bundler runtime reference into the page.
|
|
1262
|
+
return `(function () {
|
|
1263
|
+
const __name = (value) => value;
|
|
1264
|
+
return (${captureRenderedWebsiteContext.toString()})();
|
|
1265
|
+
})()`;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
export function boundWebsiteExtraction(
|
|
1269
|
+
extraction: WebsiteExtraction,
|
|
1270
|
+
): WebsiteExtraction {
|
|
1271
|
+
const boundedComponents = extraction.designTokens.components
|
|
1272
|
+
?.slice(0, MAX_COMPONENT_STYLES)
|
|
1273
|
+
.map((component) =>
|
|
1274
|
+
Object.fromEntries(
|
|
1275
|
+
Object.entries(component).map(([key, value]) => [
|
|
1276
|
+
key,
|
|
1277
|
+
typeof value === "string" ? value.slice(0, 500) : value,
|
|
1278
|
+
]),
|
|
1279
|
+
),
|
|
1280
|
+
) as WebsiteExtraction["designTokens"]["components"];
|
|
1281
|
+
return {
|
|
1282
|
+
title: normalizeWhitespace(extraction.title).slice(0, 500),
|
|
1283
|
+
text: normalizeWhitespace(extraction.text).slice(
|
|
1284
|
+
0,
|
|
1285
|
+
MAX_RENDERED_TEXT_CHARS,
|
|
1286
|
+
),
|
|
1287
|
+
assets: extraction.assets.slice(0, MAX_EXTRACTED_ASSETS).map((asset) => ({
|
|
1288
|
+
...asset,
|
|
1289
|
+
url: asset.url.slice(0, 4_096),
|
|
1290
|
+
})),
|
|
1291
|
+
internalLinks: extraction.internalLinks
|
|
1292
|
+
.slice(0, MAX_INTERNAL_LINKS)
|
|
1293
|
+
.map((url) => url.slice(0, 4_096)),
|
|
1294
|
+
designTokens: {
|
|
1295
|
+
colors: extraction.designTokens.colors.slice(0, MAX_COLORS),
|
|
1296
|
+
typography: extraction.designTokens.typography.slice(0, MAX_TYPOGRAPHY),
|
|
1297
|
+
spacing: extraction.designTokens.spacing.slice(0, MAX_SPACING),
|
|
1298
|
+
radii: extraction.designTokens.radii.slice(0, MAX_RADII),
|
|
1299
|
+
cssVariables: Object.fromEntries(
|
|
1300
|
+
Object.entries(extraction.designTokens.cssVariables)
|
|
1301
|
+
.slice(0, MAX_CSS_VARIABLES)
|
|
1302
|
+
.map(([name, value]) => [name.slice(0, 500), value.slice(0, 4_096)]),
|
|
1303
|
+
),
|
|
1304
|
+
...(extraction.designTokens.semanticColors
|
|
1305
|
+
? {
|
|
1306
|
+
semanticColors: Object.fromEntries(
|
|
1307
|
+
Object.entries(extraction.designTokens.semanticColors)
|
|
1308
|
+
.filter(([, value]) => typeof value === "string" && value)
|
|
1309
|
+
.map(([name, value]) => [name, value.slice(0, 200)]),
|
|
1310
|
+
),
|
|
1311
|
+
}
|
|
1312
|
+
: {}),
|
|
1313
|
+
...(extraction.designTokens.shadows
|
|
1314
|
+
? {
|
|
1315
|
+
shadows: extraction.designTokens.shadows
|
|
1316
|
+
.slice(0, 32)
|
|
1317
|
+
.map((value) => value.slice(0, 500)),
|
|
1318
|
+
}
|
|
1319
|
+
: {}),
|
|
1320
|
+
...(extraction.designTokens.backgrounds
|
|
1321
|
+
? {
|
|
1322
|
+
backgrounds: extraction.designTokens.backgrounds
|
|
1323
|
+
.slice(0, 32)
|
|
1324
|
+
.map((value) => value.slice(0, 500)),
|
|
1325
|
+
}
|
|
1326
|
+
: {}),
|
|
1327
|
+
...(boundedComponents ? { components: boundedComponents } : {}),
|
|
1328
|
+
...(extraction.designTokens.layout
|
|
1329
|
+
? {
|
|
1330
|
+
layout: Object.fromEntries(
|
|
1331
|
+
Object.entries(extraction.designTokens.layout)
|
|
1332
|
+
.filter(([, value]) => typeof value === "string" && value)
|
|
1333
|
+
.map(([name, value]) => [name, value.slice(0, 200)]),
|
|
1334
|
+
),
|
|
1335
|
+
}
|
|
1336
|
+
: {}),
|
|
1337
|
+
},
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
function emptyExtraction(): WebsiteExtraction {
|
|
1342
|
+
return {
|
|
1343
|
+
title: "",
|
|
1344
|
+
text: "",
|
|
1345
|
+
assets: [],
|
|
1346
|
+
internalLinks: [],
|
|
1347
|
+
designTokens: {
|
|
1348
|
+
colors: [],
|
|
1349
|
+
typography: [],
|
|
1350
|
+
spacing: [],
|
|
1351
|
+
radii: [],
|
|
1352
|
+
cssVariables: {},
|
|
1353
|
+
},
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
function classifyWebsite(
|
|
1358
|
+
extraction: WebsiteExtraction,
|
|
1359
|
+
finalUrl: string,
|
|
1360
|
+
): RenderedPageResult["classification"] {
|
|
1361
|
+
const path = new URL(finalUrl).pathname.replace(/\/+$/, "") || "/";
|
|
1362
|
+
const sample =
|
|
1363
|
+
`${extraction.title} ${extraction.text.slice(0, 2_000)}`.toLowerCase();
|
|
1364
|
+
if (path === "/") return "homepage";
|
|
1365
|
+
if (/\b(api|docs?|guide|reference|tutorial)\b/.test(sample)) {
|
|
1366
|
+
return "documentation";
|
|
1367
|
+
}
|
|
1368
|
+
if (/\b(blog|article|news|author|published)\b/.test(sample)) return "content";
|
|
1369
|
+
if (/\b(pricing|features|customers|solutions|product)\b/.test(sample)) {
|
|
1370
|
+
return "marketing";
|
|
1371
|
+
}
|
|
1372
|
+
return "unknown";
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
function boundedTimeout(value: number | undefined): number {
|
|
1376
|
+
return Number.isFinite(value)
|
|
1377
|
+
? Math.max(1_000, Math.min(120_000, Math.floor(value!)))
|
|
1378
|
+
: 30_000;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
function boundedScreenshot(value: Uint8Array): Uint8Array | undefined {
|
|
1382
|
+
return value.byteLength <= MAX_SCREENSHOT_BYTES ? value : undefined;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
function errorMessage(error: unknown): string {
|
|
1386
|
+
return error instanceof Error ? error.message : String(error);
|
|
1387
|
+
}
|