import {
toDataUri,
type InlineReport,
type InlineOptions,
type FetchAsset,
} from './inline-assets-shared';
import { buildInlinedImportmap } from './inline-assets-importmap';
export { toDataUri } from './inline-assets-shared';
export type { InlineReport, InlineOptions, FetchAsset } from './inline-assets-shared';
export type AssetRefKind = 'link' | 'script' | 'img' | 'source' | 'css-url' | 'importmap';
export interface AssetRef {
kind: AssetRefKind;
url: string;
}
const HTTP_URL = /^https?:\/\//i;
/** Scan LLM-generated interactive HTML for external http(s) asset references. */
export function collectAssetRefs(html: string): AssetRef[] {
const refs: AssetRef[] = [];
const push = (kind: AssetRefKind, url: string) => {
if (HTTP_URL.test(url)) refs.push({ kind, url });
};
for (const m of html.matchAll(/]*?\bhref\s*=\s*["']([^"']+)["'][^>]*>/gi)) {
push('link', m[1]);
}
for (const m of html.matchAll(/`;
},
);
}
export async function inlineHtmlAssets(
html: string,
options?: InlineOptions,
): Promise<{ html: string; report: InlineReport }> {
const fetchAsset = options?.fetcher ?? createAssetFetcher(options);
const report: InlineReport = { inlined: [], failed: [] };
// Pre-warm non-importmap asset fetches in parallel so the sequential
// replaceAsync passes below hit a warm cache (fonts are parallelized
// inside inlineCssUrls; importmap modules are handled in buildInlinedImportmap).
await Promise.all(
collectAssetRefs(html)
.filter((r) => r.kind !== 'importmap')
.map((r) => fetchAsset(r.url).catch(() => null)),
);
let out = html;
const markInlined = (url: string) => {
if (!report.inlined.includes(url)) report.inlined.push(url);
};
const markFailed = (url: string, reason: string) => {
if (!report.failed.some((f) => f.url === url)) report.failed.push({ url, reason });
};
// 1) → `;
},
);
// 2)