@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.
Files changed (695) hide show
  1. package/README.md +154 -0
  2. package/agent-native.eject.json +41 -0
  3. package/agent-native.package.json +119 -0
  4. package/dist/actions/confirm-canonical-logo.d.ts +6 -0
  5. package/dist/actions/confirm-canonical-logo.d.ts.map +1 -0
  6. package/dist/actions/confirm-canonical-logo.js +19 -0
  7. package/dist/actions/confirm-canonical-logo.js.map +1 -0
  8. package/dist/actions/continue-context-import.d.ts +22 -0
  9. package/dist/actions/continue-context-import.d.ts.map +1 -0
  10. package/dist/actions/continue-context-import.js +13 -0
  11. package/dist/actions/continue-context-import.js.map +1 -0
  12. package/dist/actions/creative-context-a2a.d.ts +10 -0
  13. package/dist/actions/creative-context-a2a.d.ts.map +1 -0
  14. package/dist/actions/creative-context-a2a.js +66 -0
  15. package/dist/actions/creative-context-a2a.js.map +1 -0
  16. package/dist/actions/get-brand-profile.d.ts +33 -0
  17. package/dist/actions/get-brand-profile.d.ts.map +1 -0
  18. package/dist/actions/get-brand-profile.js +13 -0
  19. package/dist/actions/get-brand-profile.js.map +1 -0
  20. package/dist/actions/get-context-import-status.d.ts +22 -0
  21. package/dist/actions/get-context-import-status.d.ts.map +1 -0
  22. package/dist/actions/get-context-import-status.js +12 -0
  23. package/dist/actions/get-context-import-status.js.map +1 -0
  24. package/dist/actions/get-context-item.d.ts +2 -0
  25. package/dist/actions/get-context-item.d.ts.map +1 -0
  26. package/dist/actions/get-context-item.js +2 -0
  27. package/dist/actions/get-context-item.js.map +1 -0
  28. package/dist/actions/get-context-pack.d.ts +7 -0
  29. package/dist/actions/get-context-pack.d.ts.map +1 -0
  30. package/dist/actions/get-context-pack.js +12 -0
  31. package/dist/actions/get-context-pack.js.map +1 -0
  32. package/dist/actions/get-creative-context.d.ts +157 -0
  33. package/dist/actions/get-creative-context.d.ts.map +1 -0
  34. package/dist/actions/get-creative-context.js +148 -0
  35. package/dist/actions/get-creative-context.js.map +1 -0
  36. package/dist/actions/get-google-picker-session.d.ts +10 -0
  37. package/dist/actions/get-google-picker-session.d.ts.map +1 -0
  38. package/dist/actions/get-google-picker-session.js +38 -0
  39. package/dist/actions/get-google-picker-session.js.map +1 -0
  40. package/dist/actions/index.d.ts +3 -0
  41. package/dist/actions/index.d.ts.map +1 -0
  42. package/dist/actions/index.js +73 -0
  43. package/dist/actions/index.js.map +1 -0
  44. package/dist/actions/infer-brand-dna.d.ts +52 -0
  45. package/dist/actions/infer-brand-dna.d.ts.map +1 -0
  46. package/dist/actions/infer-brand-dna.js +13 -0
  47. package/dist/actions/infer-brand-dna.js.map +1 -0
  48. package/dist/actions/list-canonical-logo-candidates.d.ts +9 -0
  49. package/dist/actions/list-canonical-logo-candidates.d.ts.map +1 -0
  50. package/dist/actions/list-canonical-logo-candidates.js +82 -0
  51. package/dist/actions/list-canonical-logo-candidates.js.map +1 -0
  52. package/dist/actions/list-context-connections.d.ts +22 -0
  53. package/dist/actions/list-context-connections.d.ts.map +1 -0
  54. package/dist/actions/list-context-connections.js +43 -0
  55. package/dist/actions/list-context-connections.js.map +1 -0
  56. package/dist/actions/list-context-memberships.d.ts +11 -0
  57. package/dist/actions/list-context-memberships.d.ts.map +1 -0
  58. package/dist/actions/list-context-memberships.js +47 -0
  59. package/dist/actions/list-context-memberships.js.map +1 -0
  60. package/dist/actions/list-context-packs.d.ts +10 -0
  61. package/dist/actions/list-context-packs.d.ts.map +1 -0
  62. package/dist/actions/list-context-packs.js +16 -0
  63. package/dist/actions/list-context-packs.js.map +1 -0
  64. package/dist/actions/list-context-sources.d.ts +12 -0
  65. package/dist/actions/list-context-sources.d.ts.map +1 -0
  66. package/dist/actions/list-context-sources.js +20 -0
  67. package/dist/actions/list-context-sources.js.map +1 -0
  68. package/dist/actions/list-context-suggestions.d.ts +13 -0
  69. package/dist/actions/list-context-suggestions.d.ts.map +1 -0
  70. package/dist/actions/list-context-suggestions.js +28 -0
  71. package/dist/actions/list-context-suggestions.js.map +1 -0
  72. package/dist/actions/list-creative-contexts.d.ts +12 -0
  73. package/dist/actions/list-creative-contexts.d.ts.map +1 -0
  74. package/dist/actions/list-creative-contexts.js +28 -0
  75. package/dist/actions/list-creative-contexts.js.map +1 -0
  76. package/dist/actions/manage-brand-profile.d.ts +28 -0
  77. package/dist/actions/manage-brand-profile.d.ts.map +1 -0
  78. package/dist/actions/manage-brand-profile.js +47 -0
  79. package/dist/actions/manage-brand-profile.js.map +1 -0
  80. package/dist/actions/manage-context-membership.d.ts +64 -0
  81. package/dist/actions/manage-context-membership.d.ts.map +1 -0
  82. package/dist/actions/manage-context-membership.js +70 -0
  83. package/dist/actions/manage-context-membership.js.map +1 -0
  84. package/dist/actions/manage-context-pack.d.ts +36 -0
  85. package/dist/actions/manage-context-pack.d.ts.map +1 -0
  86. package/dist/actions/manage-context-pack.js +71 -0
  87. package/dist/actions/manage-context-pack.js.map +1 -0
  88. package/dist/actions/manage-context-source.d.ts +51 -0
  89. package/dist/actions/manage-context-source.d.ts.map +1 -0
  90. package/dist/actions/manage-context-source.js +130 -0
  91. package/dist/actions/manage-context-source.js.map +1 -0
  92. package/dist/actions/manage-creative-context.d.ts +28 -0
  93. package/dist/actions/manage-creative-context.d.ts.map +1 -0
  94. package/dist/actions/manage-creative-context.js +65 -0
  95. package/dist/actions/manage-creative-context.js.map +1 -0
  96. package/dist/actions/manage-layout-template.d.ts +13 -0
  97. package/dist/actions/manage-layout-template.d.ts.map +1 -0
  98. package/dist/actions/manage-layout-template.js +43 -0
  99. package/dist/actions/manage-layout-template.js.map +1 -0
  100. package/dist/actions/preview-context-import.d.ts +23 -0
  101. package/dist/actions/preview-context-import.d.ts.map +1 -0
  102. package/dist/actions/preview-context-import.js +57 -0
  103. package/dist/actions/preview-context-import.js.map +1 -0
  104. package/dist/actions/process-context-purge.d.ts +37 -0
  105. package/dist/actions/process-context-purge.d.ts.map +1 -0
  106. package/dist/actions/process-context-purge.js +20 -0
  107. package/dist/actions/process-context-purge.js.map +1 -0
  108. package/dist/actions/propose-brand-dna.d.ts +15 -0
  109. package/dist/actions/propose-brand-dna.d.ts.map +1 -0
  110. package/dist/actions/propose-brand-dna.js +19 -0
  111. package/dist/actions/propose-brand-dna.js.map +1 -0
  112. package/dist/actions/propose-canonical-logo.d.ts +9 -0
  113. package/dist/actions/propose-canonical-logo.d.ts.map +1 -0
  114. package/dist/actions/propose-canonical-logo.js +16 -0
  115. package/dist/actions/propose-canonical-logo.js.map +1 -0
  116. package/dist/actions/publish-brand-dna.d.ts +13 -0
  117. package/dist/actions/publish-brand-dna.d.ts.map +1 -0
  118. package/dist/actions/publish-brand-dna.js +23 -0
  119. package/dist/actions/publish-brand-dna.js.map +1 -0
  120. package/dist/actions/recommend-context-roots.d.ts +14 -0
  121. package/dist/actions/recommend-context-roots.d.ts.map +1 -0
  122. package/dist/actions/recommend-context-roots.js +22 -0
  123. package/dist/actions/recommend-context-roots.js.map +1 -0
  124. package/dist/actions/record-context-feedback.d.ts +10 -0
  125. package/dist/actions/record-context-feedback.d.ts.map +1 -0
  126. package/dist/actions/record-context-feedback.js +14 -0
  127. package/dist/actions/record-context-feedback.js.map +1 -0
  128. package/dist/actions/review-context-items.d.ts +35 -0
  129. package/dist/actions/review-context-items.d.ts.map +1 -0
  130. package/dist/actions/review-context-items.js +65 -0
  131. package/dist/actions/review-context-items.js.map +1 -0
  132. package/dist/actions/run-embedding-bakeoff.d.ts +15 -0
  133. package/dist/actions/run-embedding-bakeoff.d.ts.map +1 -0
  134. package/dist/actions/run-embedding-bakeoff.js +72 -0
  135. package/dist/actions/run-embedding-bakeoff.js.map +1 -0
  136. package/dist/actions/search-creative-context.d.ts +80 -0
  137. package/dist/actions/search-creative-context.d.ts.map +1 -0
  138. package/dist/actions/search-creative-context.js +42 -0
  139. package/dist/actions/search-creative-context.js.map +1 -0
  140. package/dist/actions/set-brand-dna.d.ts +15 -0
  141. package/dist/actions/set-brand-dna.d.ts.map +1 -0
  142. package/dist/actions/set-brand-dna.js +19 -0
  143. package/dist/actions/set-brand-dna.js.map +1 -0
  144. package/dist/actions/start-context-enrichment.d.ts +25 -0
  145. package/dist/actions/start-context-enrichment.d.ts.map +1 -0
  146. package/dist/actions/start-context-enrichment.js +48 -0
  147. package/dist/actions/start-context-enrichment.js.map +1 -0
  148. package/dist/actions/start-context-import.d.ts +24 -0
  149. package/dist/actions/start-context-import.d.ts.map +1 -0
  150. package/dist/actions/start-context-import.js +37 -0
  151. package/dist/actions/start-context-import.js.map +1 -0
  152. package/dist/client/CreativeContextChip.d.ts +18 -0
  153. package/dist/client/CreativeContextChip.d.ts.map +1 -0
  154. package/dist/client/CreativeContextChip.js +80 -0
  155. package/dist/client/CreativeContextChip.js.map +1 -0
  156. package/dist/client/CreativeContextPanel.d.ts +73 -0
  157. package/dist/client/CreativeContextPanel.d.ts.map +1 -0
  158. package/dist/client/CreativeContextPanel.js +1171 -0
  159. package/dist/client/CreativeContextPanel.js.map +1 -0
  160. package/dist/client/CreativeContextSettingsLink.d.ts +4 -0
  161. package/dist/client/CreativeContextSettingsLink.d.ts.map +1 -0
  162. package/dist/client/CreativeContextSettingsLink.js +8 -0
  163. package/dist/client/CreativeContextSettingsLink.js.map +1 -0
  164. package/dist/client/CreativeContextShareTab.d.ts +56 -0
  165. package/dist/client/CreativeContextShareTab.d.ts.map +1 -0
  166. package/dist/client/CreativeContextShareTab.js +213 -0
  167. package/dist/client/CreativeContextShareTab.js.map +1 -0
  168. package/dist/client/actions.d.ts +474 -0
  169. package/dist/client/actions.d.ts.map +1 -0
  170. package/dist/client/actions.js +411 -0
  171. package/dist/client/actions.js.map +1 -0
  172. package/dist/client/agent-tab.d.ts +10 -0
  173. package/dist/client/agent-tab.d.ts.map +1 -0
  174. package/dist/client/agent-tab.js +32 -0
  175. package/dist/client/agent-tab.js.map +1 -0
  176. package/dist/client/application-state.d.ts +24 -0
  177. package/dist/client/application-state.d.ts.map +1 -0
  178. package/dist/client/application-state.js +111 -0
  179. package/dist/client/application-state.js.map +1 -0
  180. package/dist/client/google-slides-picker.d.ts +19 -0
  181. package/dist/client/google-slides-picker.d.ts.map +1 -0
  182. package/dist/client/google-slides-picker.js +106 -0
  183. package/dist/client/google-slides-picker.js.map +1 -0
  184. package/dist/client/index.d.ts +10 -0
  185. package/dist/client/index.d.ts.map +1 -0
  186. package/dist/client/index.js +10 -0
  187. package/dist/client/index.js.map +1 -0
  188. package/dist/client/messages.d.ts +5 -0
  189. package/dist/client/messages.d.ts.map +1 -0
  190. package/dist/client/messages.js +1158 -0
  191. package/dist/client/messages.js.map +1 -0
  192. package/dist/connectors/context.d.ts +9 -0
  193. package/dist/connectors/context.d.ts.map +1 -0
  194. package/dist/connectors/context.js +59 -0
  195. package/dist/connectors/context.js.map +1 -0
  196. package/dist/connectors/document-parser.d.ts +2 -0
  197. package/dist/connectors/document-parser.d.ts.map +1 -0
  198. package/dist/connectors/document-parser.js +2 -0
  199. package/dist/connectors/document-parser.js.map +1 -0
  200. package/dist/connectors/figma-native.d.ts +18 -0
  201. package/dist/connectors/figma-native.d.ts.map +1 -0
  202. package/dist/connectors/figma-native.js +883 -0
  203. package/dist/connectors/figma-native.js.map +1 -0
  204. package/dist/connectors/figma.d.ts +10 -0
  205. package/dist/connectors/figma.d.ts.map +1 -0
  206. package/dist/connectors/figma.js +166 -0
  207. package/dist/connectors/figma.js.map +1 -0
  208. package/dist/connectors/google-slides-native.d.ts +58 -0
  209. package/dist/connectors/google-slides-native.d.ts.map +1 -0
  210. package/dist/connectors/google-slides-native.js +1118 -0
  211. package/dist/connectors/google-slides-native.js.map +1 -0
  212. package/dist/connectors/google-slides.d.ts +15 -0
  213. package/dist/connectors/google-slides.d.ts.map +1 -0
  214. package/dist/connectors/google-slides.js +895 -0
  215. package/dist/connectors/google-slides.js.map +1 -0
  216. package/dist/connectors/index.d.ts +21 -0
  217. package/dist/connectors/index.d.ts.map +1 -0
  218. package/dist/connectors/index.js +45 -0
  219. package/dist/connectors/index.js.map +1 -0
  220. package/dist/connectors/manual.d.ts +9 -0
  221. package/dist/connectors/manual.d.ts.map +1 -0
  222. package/dist/connectors/manual.js +91 -0
  223. package/dist/connectors/manual.js.map +1 -0
  224. package/dist/connectors/normalize.d.ts +42 -0
  225. package/dist/connectors/normalize.d.ts.map +1 -0
  226. package/dist/connectors/normalize.js +335 -0
  227. package/dist/connectors/normalize.js.map +1 -0
  228. package/dist/connectors/notion.d.ts +10 -0
  229. package/dist/connectors/notion.d.ts.map +1 -0
  230. package/dist/connectors/notion.js +393 -0
  231. package/dist/connectors/notion.js.map +1 -0
  232. package/dist/connectors/private-artifacts.d.ts +39 -0
  233. package/dist/connectors/private-artifacts.d.ts.map +1 -0
  234. package/dist/connectors/private-artifacts.js +175 -0
  235. package/dist/connectors/private-artifacts.js.map +1 -0
  236. package/dist/connectors/provider-response.d.ts +20 -0
  237. package/dist/connectors/provider-response.d.ts.map +1 -0
  238. package/dist/connectors/provider-response.js +98 -0
  239. package/dist/connectors/provider-response.js.map +1 -0
  240. package/dist/connectors/recommendations.d.ts +25 -0
  241. package/dist/connectors/recommendations.d.ts.map +1 -0
  242. package/dist/connectors/recommendations.js +172 -0
  243. package/dist/connectors/recommendations.js.map +1 -0
  244. package/dist/connectors/registry.d.ts +10 -0
  245. package/dist/connectors/registry.d.ts.map +1 -0
  246. package/dist/connectors/registry.js +34 -0
  247. package/dist/connectors/registry.js.map +1 -0
  248. package/dist/connectors/rendered-design.d.ts +68 -0
  249. package/dist/connectors/rendered-design.d.ts.map +1 -0
  250. package/dist/connectors/rendered-design.js +362 -0
  251. package/dist/connectors/rendered-design.js.map +1 -0
  252. package/dist/connectors/rendered-page.d.ts +115 -0
  253. package/dist/connectors/rendered-page.d.ts.map +1 -0
  254. package/dist/connectors/rendered-page.js +1036 -0
  255. package/dist/connectors/rendered-page.js.map +1 -0
  256. package/dist/connectors/smart-defaults.d.ts +9 -0
  257. package/dist/connectors/smart-defaults.d.ts.map +1 -0
  258. package/dist/connectors/smart-defaults.js +72 -0
  259. package/dist/connectors/smart-defaults.js.map +1 -0
  260. package/dist/connectors/types.d.ts +79 -0
  261. package/dist/connectors/types.d.ts.map +1 -0
  262. package/dist/connectors/types.js +2 -0
  263. package/dist/connectors/types.js.map +1 -0
  264. package/dist/connectors/upload.d.ts +9 -0
  265. package/dist/connectors/upload.d.ts.map +1 -0
  266. package/dist/connectors/upload.js +440 -0
  267. package/dist/connectors/upload.js.map +1 -0
  268. package/dist/connectors/website.d.ts +20 -0
  269. package/dist/connectors/website.d.ts.map +1 -0
  270. package/dist/connectors/website.js +616 -0
  271. package/dist/connectors/website.js.map +1 -0
  272. package/dist/embeddings/index.d.ts +3 -0
  273. package/dist/embeddings/index.d.ts.map +1 -0
  274. package/dist/embeddings/index.js +3 -0
  275. package/dist/embeddings/index.js.map +1 -0
  276. package/dist/embeddings/providers.d.ts +2 -0
  277. package/dist/embeddings/providers.d.ts.map +1 -0
  278. package/dist/embeddings/providers.js +2 -0
  279. package/dist/embeddings/providers.js.map +1 -0
  280. package/dist/embeddings/types.d.ts +2 -0
  281. package/dist/embeddings/types.d.ts.map +1 -0
  282. package/dist/embeddings/types.js +2 -0
  283. package/dist/embeddings/types.js.map +1 -0
  284. package/dist/eval/acceptance-fixtures.d.ts +3 -0
  285. package/dist/eval/acceptance-fixtures.d.ts.map +1 -0
  286. package/dist/eval/acceptance-fixtures.js +84 -0
  287. package/dist/eval/acceptance-fixtures.js.map +1 -0
  288. package/dist/eval/acceptance.d.ts +114 -0
  289. package/dist/eval/acceptance.d.ts.map +1 -0
  290. package/dist/eval/acceptance.js +260 -0
  291. package/dist/eval/acceptance.js.map +1 -0
  292. package/dist/eval/fixtures.d.ts +16 -0
  293. package/dist/eval/fixtures.d.ts.map +1 -0
  294. package/dist/eval/fixtures.js +119 -0
  295. package/dist/eval/fixtures.js.map +1 -0
  296. package/dist/eval/index.d.ts +63 -0
  297. package/dist/eval/index.d.ts.map +1 -0
  298. package/dist/eval/index.js +174 -0
  299. package/dist/eval/index.js.map +1 -0
  300. package/dist/index.d.ts +5 -0
  301. package/dist/index.d.ts.map +1 -0
  302. package/dist/index.js +5 -0
  303. package/dist/index.js.map +1 -0
  304. package/dist/jobs/background-worker.d.ts +31 -0
  305. package/dist/jobs/background-worker.d.ts.map +1 -0
  306. package/dist/jobs/background-worker.js +315 -0
  307. package/dist/jobs/background-worker.js.map +1 -0
  308. package/dist/jobs/import-runner.d.ts +3 -0
  309. package/dist/jobs/import-runner.d.ts.map +1 -0
  310. package/dist/jobs/import-runner.js +613 -0
  311. package/dist/jobs/import-runner.js.map +1 -0
  312. package/dist/jobs/index.d.ts +9 -0
  313. package/dist/jobs/index.d.ts.map +1 -0
  314. package/dist/jobs/index.js +8 -0
  315. package/dist/jobs/index.js.map +1 -0
  316. package/dist/jobs/progress.d.ts +3 -0
  317. package/dist/jobs/progress.d.ts.map +1 -0
  318. package/dist/jobs/progress.js +30 -0
  319. package/dist/jobs/progress.js.map +1 -0
  320. package/dist/jobs/rebuild.d.ts +13 -0
  321. package/dist/jobs/rebuild.d.ts.map +1 -0
  322. package/dist/jobs/rebuild.js +178 -0
  323. package/dist/jobs/rebuild.js.map +1 -0
  324. package/dist/jobs/server-worker.d.ts +16 -0
  325. package/dist/jobs/server-worker.d.ts.map +1 -0
  326. package/dist/jobs/server-worker.js +262 -0
  327. package/dist/jobs/server-worker.js.map +1 -0
  328. package/dist/jobs/store-adapter.d.ts +3 -0
  329. package/dist/jobs/store-adapter.d.ts.map +1 -0
  330. package/dist/jobs/store-adapter.js +37 -0
  331. package/dist/jobs/store-adapter.js.map +1 -0
  332. package/dist/jobs/types.d.ts +138 -0
  333. package/dist/jobs/types.d.ts.map +1 -0
  334. package/dist/jobs/types.js +2 -0
  335. package/dist/jobs/types.js.map +1 -0
  336. package/dist/jobs/worker.d.ts +27 -0
  337. package/dist/jobs/worker.d.ts.map +1 -0
  338. package/dist/jobs/worker.js +90 -0
  339. package/dist/jobs/worker.js.map +1 -0
  340. package/dist/media-url.d.ts +7 -0
  341. package/dist/media-url.d.ts.map +1 -0
  342. package/dist/media-url.js +15 -0
  343. package/dist/media-url.js.map +1 -0
  344. package/dist/native-artifact-reassembly.d.ts +25 -0
  345. package/dist/native-artifact-reassembly.d.ts.map +1 -0
  346. package/dist/native-artifact-reassembly.js +262 -0
  347. package/dist/native-artifact-reassembly.js.map +1 -0
  348. package/dist/native-artifact.d.ts +88 -0
  349. package/dist/native-artifact.d.ts.map +1 -0
  350. package/dist/native-artifact.js +86 -0
  351. package/dist/native-artifact.js.map +1 -0
  352. package/dist/schema/index.d.ts +7111 -0
  353. package/dist/schema/index.d.ts.map +1 -0
  354. package/dist/schema/index.js +469 -0
  355. package/dist/schema/index.js.map +1 -0
  356. package/dist/schema/migrations.d.ts +10 -0
  357. package/dist/schema/migrations.d.ts.map +1 -0
  358. package/dist/schema/migrations.js +333 -0
  359. package/dist/schema/migrations.js.map +1 -0
  360. package/dist/search/fusion.d.ts +2 -0
  361. package/dist/search/fusion.d.ts.map +1 -0
  362. package/dist/search/fusion.js +2 -0
  363. package/dist/search/fusion.js.map +1 -0
  364. package/dist/search/mode.d.ts +4 -0
  365. package/dist/search/mode.d.ts.map +1 -0
  366. package/dist/search/mode.js +13 -0
  367. package/dist/search/mode.js.map +1 -0
  368. package/dist/search/postgres-fts.d.ts +2 -0
  369. package/dist/search/postgres-fts.d.ts.map +1 -0
  370. package/dist/search/postgres-fts.js +2 -0
  371. package/dist/search/postgres-fts.js.map +1 -0
  372. package/dist/server/brand-context.d.ts +15 -0
  373. package/dist/server/brand-context.d.ts.map +1 -0
  374. package/dist/server/brand-context.js +149 -0
  375. package/dist/server/brand-context.js.map +1 -0
  376. package/dist/server/context.d.ts +77 -0
  377. package/dist/server/context.d.ts.map +1 -0
  378. package/dist/server/context.js +63 -0
  379. package/dist/server/context.js.map +1 -0
  380. package/dist/server/enrichment.d.ts +24 -0
  381. package/dist/server/enrichment.d.ts.map +1 -0
  382. package/dist/server/enrichment.js +138 -0
  383. package/dist/server/enrichment.js.map +1 -0
  384. package/dist/server/generation-artifact-access.d.ts +21 -0
  385. package/dist/server/generation-artifact-access.d.ts.map +1 -0
  386. package/dist/server/generation-artifact-access.js +113 -0
  387. package/dist/server/generation-artifact-access.js.map +1 -0
  388. package/dist/server/generation-context.d.ts +220 -0
  389. package/dist/server/generation-context.d.ts.map +1 -0
  390. package/dist/server/generation-context.js +432 -0
  391. package/dist/server/generation-context.js.map +1 -0
  392. package/dist/server/index.d.ts +27 -0
  393. package/dist/server/index.d.ts.map +1 -0
  394. package/dist/server/index.js +115 -0
  395. package/dist/server/index.js.map +1 -0
  396. package/dist/server/isolated-a2a.d.ts +175 -0
  397. package/dist/server/isolated-a2a.d.ts.map +1 -0
  398. package/dist/server/isolated-a2a.js +312 -0
  399. package/dist/server/isolated-a2a.js.map +1 -0
  400. package/dist/server/media.d.ts +15 -0
  401. package/dist/server/media.d.ts.map +1 -0
  402. package/dist/server/media.js +108 -0
  403. package/dist/server/media.js.map +1 -0
  404. package/dist/server/native-resource-capture.d.ts +78 -0
  405. package/dist/server/native-resource-capture.d.ts.map +1 -0
  406. package/dist/server/native-resource-capture.js +94 -0
  407. package/dist/server/native-resource-capture.js.map +1 -0
  408. package/dist/server/prompt-provider.d.ts +5 -0
  409. package/dist/server/prompt-provider.d.ts.map +1 -0
  410. package/dist/server/prompt-provider.js +59 -0
  411. package/dist/server/prompt-provider.js.map +1 -0
  412. package/dist/server/public-serialization.d.ts +160 -0
  413. package/dist/server/public-serialization.d.ts.map +1 -0
  414. package/dist/server/public-serialization.js +108 -0
  415. package/dist/server/public-serialization.js.map +1 -0
  416. package/dist/server/resource-paths.d.ts +3 -0
  417. package/dist/server/resource-paths.d.ts.map +1 -0
  418. package/dist/server/resource-paths.js +5 -0
  419. package/dist/server/resource-paths.js.map +1 -0
  420. package/dist/server/retrieval.d.ts +83 -0
  421. package/dist/server/retrieval.d.ts.map +1 -0
  422. package/dist/server/retrieval.js +445 -0
  423. package/dist/server/retrieval.js.map +1 -0
  424. package/dist/server/safe-native-preview.d.ts +15 -0
  425. package/dist/server/safe-native-preview.d.ts.map +1 -0
  426. package/dist/server/safe-native-preview.js +148 -0
  427. package/dist/server/safe-native-preview.js.map +1 -0
  428. package/dist/store/brand.d.ts +47 -0
  429. package/dist/store/brand.d.ts.map +1 -0
  430. package/dist/store/brand.js +346 -0
  431. package/dist/store/brand.js.map +1 -0
  432. package/dist/store/content.d.ts +134 -0
  433. package/dist/store/content.d.ts.map +1 -0
  434. package/dist/store/content.js +1677 -0
  435. package/dist/store/content.js.map +1 -0
  436. package/dist/store/contexts.d.ts +216 -0
  437. package/dist/store/contexts.d.ts.map +1 -0
  438. package/dist/store/contexts.js +1346 -0
  439. package/dist/store/contexts.js.map +1 -0
  440. package/dist/store/generation.d.ts +28 -0
  441. package/dist/store/generation.d.ts.map +1 -0
  442. package/dist/store/generation.js +140 -0
  443. package/dist/store/generation.js.map +1 -0
  444. package/dist/store/helpers.d.ts +12 -0
  445. package/dist/store/helpers.d.ts.map +1 -0
  446. package/dist/store/helpers.js +41 -0
  447. package/dist/store/helpers.js.map +1 -0
  448. package/dist/store/index.d.ts +11 -0
  449. package/dist/store/index.d.ts.map +1 -0
  450. package/dist/store/index.js +11 -0
  451. package/dist/store/index.js.map +1 -0
  452. package/dist/store/inference.d.ts +57 -0
  453. package/dist/store/inference.d.ts.map +1 -0
  454. package/dist/store/inference.js +279 -0
  455. package/dist/store/inference.js.map +1 -0
  456. package/dist/store/jobs.d.ts +80 -0
  457. package/dist/store/jobs.d.ts.map +1 -0
  458. package/dist/store/jobs.js +428 -0
  459. package/dist/store/jobs.js.map +1 -0
  460. package/dist/store/native-artifact-summary.d.ts +7 -0
  461. package/dist/store/native-artifact-summary.d.ts.map +1 -0
  462. package/dist/store/native-artifact-summary.js +10 -0
  463. package/dist/store/native-artifact-summary.js.map +1 -0
  464. package/dist/store/packs.d.ts +36 -0
  465. package/dist/store/packs.d.ts.map +1 -0
  466. package/dist/store/packs.js +438 -0
  467. package/dist/store/packs.js.map +1 -0
  468. package/dist/store/purge.d.ts +16 -0
  469. package/dist/store/purge.d.ts.map +1 -0
  470. package/dist/store/purge.js +354 -0
  471. package/dist/store/purge.js.map +1 -0
  472. package/dist/store/sources.d.ts +76 -0
  473. package/dist/store/sources.d.ts.map +1 -0
  474. package/dist/store/sources.js +525 -0
  475. package/dist/store/sources.js.map +1 -0
  476. package/dist/store/suggestions.d.ts +29 -0
  477. package/dist/store/suggestions.d.ts.map +1 -0
  478. package/dist/store/suggestions.js +270 -0
  479. package/dist/store/suggestions.js.map +1 -0
  480. package/dist/types.d.ts +578 -0
  481. package/dist/types.d.ts.map +1 -0
  482. package/dist/types.js +10 -0
  483. package/dist/types.js.map +1 -0
  484. package/dist/untrusted-reference.d.ts +8 -0
  485. package/dist/untrusted-reference.d.ts.map +1 -0
  486. package/dist/untrusted-reference.js +19 -0
  487. package/dist/untrusted-reference.js.map +1 -0
  488. package/dist/vector/index.d.ts +2 -0
  489. package/dist/vector/index.d.ts.map +1 -0
  490. package/dist/vector/index.js +2 -0
  491. package/dist/vector/index.js.map +1 -0
  492. package/dist/vector/pgvector.d.ts +2 -0
  493. package/dist/vector/pgvector.d.ts.map +1 -0
  494. package/dist/vector/pgvector.js +2 -0
  495. package/dist/vector/pgvector.js.map +1 -0
  496. package/docs/evaluation.md +92 -0
  497. package/docs/llms-full.txt +168 -0
  498. package/docs/llms.txt +76 -0
  499. package/docs/skills/creative-context/SKILL.md +90 -0
  500. package/package.json +100 -0
  501. package/src/actions/agent-input-schemas.spec.ts +46 -0
  502. package/src/actions/confirm-canonical-logo.ts +20 -0
  503. package/src/actions/continue-context-import.ts +15 -0
  504. package/src/actions/creative-context-a2a.spec.ts +162 -0
  505. package/src/actions/creative-context-a2a.ts +88 -0
  506. package/src/actions/get-brand-profile.ts +16 -0
  507. package/src/actions/get-context-import-status.ts +13 -0
  508. package/src/actions/get-context-item.ts +1 -0
  509. package/src/actions/get-context-pack.ts +14 -0
  510. package/src/actions/get-creative-context.spec.ts +435 -0
  511. package/src/actions/get-creative-context.ts +197 -0
  512. package/src/actions/get-google-picker-session.spec.ts +62 -0
  513. package/src/actions/get-google-picker-session.ts +45 -0
  514. package/src/actions/index.ts +75 -0
  515. package/src/actions/infer-brand-dna.ts +15 -0
  516. package/src/actions/job-public-serialization.spec.ts +143 -0
  517. package/src/actions/list-canonical-logo-candidates.ts +121 -0
  518. package/src/actions/list-context-connections.spec.ts +23 -0
  519. package/src/actions/list-context-connections.ts +51 -0
  520. package/src/actions/list-context-memberships.ts +56 -0
  521. package/src/actions/list-context-packs.ts +17 -0
  522. package/src/actions/list-context-sources.ts +21 -0
  523. package/src/actions/list-context-suggestions.ts +30 -0
  524. package/src/actions/list-creative-contexts.spec.ts +38 -0
  525. package/src/actions/list-creative-contexts.ts +32 -0
  526. package/src/actions/manage-brand-profile.ts +58 -0
  527. package/src/actions/manage-context-membership.ts +80 -0
  528. package/src/actions/manage-context-pack.ts +81 -0
  529. package/src/actions/manage-context-source.ts +151 -0
  530. package/src/actions/manage-creative-context.ts +73 -0
  531. package/src/actions/manage-layout-template.ts +51 -0
  532. package/src/actions/preview-context-import.spec.ts +64 -0
  533. package/src/actions/preview-context-import.ts +61 -0
  534. package/src/actions/process-context-purge.ts +28 -0
  535. package/src/actions/propose-brand-dna.ts +22 -0
  536. package/src/actions/propose-canonical-logo.ts +19 -0
  537. package/src/actions/publish-brand-dna.ts +25 -0
  538. package/src/actions/recommend-context-roots.ts +24 -0
  539. package/src/actions/record-context-feedback.ts +16 -0
  540. package/src/actions/review-context-items.ts +69 -0
  541. package/src/actions/run-embedding-bakeoff.ts +90 -0
  542. package/src/actions/search-creative-context.ts +48 -0
  543. package/src/actions/set-brand-dna.ts +22 -0
  544. package/src/actions/start-context-enrichment.ts +52 -0
  545. package/src/actions/start-context-import.ts +38 -0
  546. package/src/client/CreativeContextChip.spec.ts +59 -0
  547. package/src/client/CreativeContextChip.tsx +243 -0
  548. package/src/client/CreativeContextPanel.tsx +3600 -0
  549. package/src/client/CreativeContextSettingsLink.tsx +30 -0
  550. package/src/client/CreativeContextShareTab.spec.ts +70 -0
  551. package/src/client/CreativeContextShareTab.tsx +666 -0
  552. package/src/client/actions.spec.ts +177 -0
  553. package/src/client/actions.ts +1024 -0
  554. package/src/client/agent-tab.tsx +56 -0
  555. package/src/client/application-state.spec.ts +46 -0
  556. package/src/client/application-state.ts +158 -0
  557. package/src/client/google-slides-picker.spec.ts +36 -0
  558. package/src/client/google-slides-picker.ts +146 -0
  559. package/src/client/index.ts +102 -0
  560. package/src/client/messages.ts +1370 -0
  561. package/src/client/source-config.spec.ts +166 -0
  562. package/src/connectors/connectors.spec.ts +778 -0
  563. package/src/connectors/context.ts +83 -0
  564. package/src/connectors/document-parser.ts +5 -0
  565. package/src/connectors/figma-native.ts +1192 -0
  566. package/src/connectors/figma-native.visual.spec.ts +221 -0
  567. package/src/connectors/figma.spec.ts +812 -0
  568. package/src/connectors/figma.ts +257 -0
  569. package/src/connectors/fixtures/google-slides-native.json +353 -0
  570. package/src/connectors/google-slides-native.spec.ts +804 -0
  571. package/src/connectors/google-slides-native.ts +1656 -0
  572. package/src/connectors/google-slides-native.visual.spec.ts +119 -0
  573. package/src/connectors/google-slides.ts +1171 -0
  574. package/src/connectors/index.ts +112 -0
  575. package/src/connectors/manual.ts +132 -0
  576. package/src/connectors/normalize.spec.ts +144 -0
  577. package/src/connectors/normalize.ts +475 -0
  578. package/src/connectors/notion.ts +529 -0
  579. package/src/connectors/private-artifacts.spec.ts +95 -0
  580. package/src/connectors/private-artifacts.ts +248 -0
  581. package/src/connectors/provider-response.ts +144 -0
  582. package/src/connectors/recommendations.ts +237 -0
  583. package/src/connectors/registry.ts +48 -0
  584. package/src/connectors/rendered-design.spec.ts +171 -0
  585. package/src/connectors/rendered-design.ts +495 -0
  586. package/src/connectors/rendered-page.spec.ts +388 -0
  587. package/src/connectors/rendered-page.ts +1387 -0
  588. package/src/connectors/smart-defaults.spec.ts +135 -0
  589. package/src/connectors/smart-defaults.ts +87 -0
  590. package/src/connectors/types.ts +113 -0
  591. package/src/connectors/upload.spec.ts +175 -0
  592. package/src/connectors/upload.ts +548 -0
  593. package/src/connectors/website.spec.ts +465 -0
  594. package/src/connectors/website.ts +777 -0
  595. package/src/embeddings/index.ts +2 -0
  596. package/src/embeddings/providers.spec.ts +73 -0
  597. package/src/embeddings/providers.ts +1 -0
  598. package/src/embeddings/types.ts +6 -0
  599. package/src/eval/acceptance-fixtures.ts +97 -0
  600. package/src/eval/acceptance.spec.ts +92 -0
  601. package/src/eval/acceptance.ts +371 -0
  602. package/src/eval/connectors-golden.spec.ts +421 -0
  603. package/src/eval/eval.spec.ts +166 -0
  604. package/src/eval/fixtures/README.md +17 -0
  605. package/src/eval/fixtures/figma-brand-system-v1.json +140 -0
  606. package/src/eval/fixtures/figma-brand-system-v2.json +127 -0
  607. package/src/eval/fixtures/launch-hero.png +0 -0
  608. package/src/eval/fixtures/launch-system-v1.pptx +0 -0
  609. package/src/eval/fixtures/launch-system-v2.pptx +0 -0
  610. package/src/eval/fixtures/notion-brand-root.json +218 -0
  611. package/src/eval/fixtures/pricing-hero.png +0 -0
  612. package/src/eval/fixtures/rendered-brand-page.html +76 -0
  613. package/src/eval/fixtures.spec.ts +43 -0
  614. package/src/eval/fixtures.ts +136 -0
  615. package/src/eval/index.ts +291 -0
  616. package/src/eval/library-flow.acceptance.spec.tsx +145 -0
  617. package/src/index.ts +4 -0
  618. package/src/jobs/background-worker.ts +423 -0
  619. package/src/jobs/import-runner.spec.ts +582 -0
  620. package/src/jobs/import-runner.ts +791 -0
  621. package/src/jobs/index.ts +40 -0
  622. package/src/jobs/progress.ts +37 -0
  623. package/src/jobs/rebuild.ts +245 -0
  624. package/src/jobs/server-worker.spec.ts +181 -0
  625. package/src/jobs/server-worker.ts +354 -0
  626. package/src/jobs/store-adapter.ts +52 -0
  627. package/src/jobs/types.ts +147 -0
  628. package/src/jobs/worker.ts +152 -0
  629. package/src/manifest.spec.ts +26 -0
  630. package/src/media-url.ts +17 -0
  631. package/src/native-artifact-reassembly.spec.ts +386 -0
  632. package/src/native-artifact-reassembly.ts +406 -0
  633. package/src/native-artifact.ts +102 -0
  634. package/src/schema/index.ts +527 -0
  635. package/src/schema/migrations.spec.ts +41 -0
  636. package/src/schema/migrations.ts +338 -0
  637. package/src/search/fusion.spec.ts +21 -0
  638. package/src/search/fusion.ts +1 -0
  639. package/src/search/mode.spec.ts +28 -0
  640. package/src/search/mode.ts +21 -0
  641. package/src/search/postgres-fts.spec.ts +63 -0
  642. package/src/search/postgres-fts.ts +1 -0
  643. package/src/server/brand-context.spec.ts +43 -0
  644. package/src/server/brand-context.ts +160 -0
  645. package/src/server/context.ts +162 -0
  646. package/src/server/enrichment.spec.ts +94 -0
  647. package/src/server/enrichment.ts +166 -0
  648. package/src/server/generation-artifact-access.spec.ts +104 -0
  649. package/src/server/generation-artifact-access.ts +217 -0
  650. package/src/server/generation-context-a2a.spec.ts +282 -0
  651. package/src/server/generation-context-off.spec.ts +213 -0
  652. package/src/server/generation-context.spec.ts +124 -0
  653. package/src/server/generation-context.ts +639 -0
  654. package/src/server/index.spec.ts +30 -0
  655. package/src/server/index.ts +163 -0
  656. package/src/server/isolated-a2a.spec.ts +184 -0
  657. package/src/server/isolated-a2a.ts +416 -0
  658. package/src/server/media.spec.ts +120 -0
  659. package/src/server/media.ts +136 -0
  660. package/src/server/native-resource-capture.spec.ts +121 -0
  661. package/src/server/native-resource-capture.ts +198 -0
  662. package/src/server/prompt-provider.spec.ts +78 -0
  663. package/src/server/prompt-provider.ts +73 -0
  664. package/src/server/public-serialization.spec.ts +150 -0
  665. package/src/server/public-serialization.ts +130 -0
  666. package/src/server/resource-paths.spec.ts +9 -0
  667. package/src/server/resource-paths.ts +5 -0
  668. package/src/server/retrieval-vector.spec.ts +180 -0
  669. package/src/server/retrieval.ts +590 -0
  670. package/src/server/safe-native-preview.spec.ts +29 -0
  671. package/src/server/safe-native-preview.ts +199 -0
  672. package/src/store/access.integration.spec.ts +3523 -0
  673. package/src/store/brand.ts +458 -0
  674. package/src/store/content.ts +2317 -0
  675. package/src/store/contexts.ts +1840 -0
  676. package/src/store/generation.ts +226 -0
  677. package/src/store/helpers.ts +53 -0
  678. package/src/store/index.ts +10 -0
  679. package/src/store/inference.spec.ts +84 -0
  680. package/src/store/inference.ts +339 -0
  681. package/src/store/invariants.spec.ts +119 -0
  682. package/src/store/jobs.ts +674 -0
  683. package/src/store/native-artifact-summary.ts +13 -0
  684. package/src/store/packs.ts +721 -0
  685. package/src/store/purge.ts +463 -0
  686. package/src/store/sources.spec.ts +102 -0
  687. package/src/store/sources.ts +692 -0
  688. package/src/store/suggestions.ts +361 -0
  689. package/src/types.ts +671 -0
  690. package/src/untrusted-reference.spec.ts +22 -0
  691. package/src/untrusted-reference.ts +24 -0
  692. package/src/vector/index.ts +1 -0
  693. package/src/vector/pgvector.spec.ts +115 -0
  694. package/src/vector/pgvector.ts +1 -0
  695. 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
+ }