import { FC, MouseEvent, ReactNode } from 'react'; import { Options } from 'react-markdown'; interface Props { content: string; } /** @internal Exported for unit tests; not part of the module's public surface. */ export declare const assistantUrlTransform: Options["urlTransform"]; /** * How a link in an assistant reply should be treated. * * ★★ `href.startsWith("/")` IS NOT AN INTERNAL TEST. A protocol-relative URL — * `//evil.example/path` — has no colon, so react-markdown's `defaultUrlTransform` * returns it verbatim and the sanitizer passes it through; the browser then * resolves it against the page's SCHEME and lands on another origin. Treating * that as internal would hand an attacker-authored destination to the router. * So the decision is made by RESOLVING the href and comparing origins. * * ★ This runs AFTER the sanitizer, so anything with an unsafe scheme has already * been reduced to an empty string upstream — `blocked` here is the empty case, * not a second scheme allow-list. The scheme policy stays in one place. */ export type AssistantLinkKind = "internal" | "external" | "fragment" | "blocked"; /** * Route an in-app PATH — `/fields?crop=42#yield`, never a full URL. Supplied by * the product; absent means "no SPA routing". */ export type AssistantNavigate = (path: string) => void; export declare function classifyAssistantHref(href: string | null | undefined, baseOrigin?: string): AssistantLinkKind; /** * The router-ready path for an href already classified as `internal`, or null if * it cannot be resolved. * * ★★ WHY THIS IS NOT THE HREF. An internal link may be written absolutely — * `https://app.example.com/fields?q=1#top` is same-origin, so it classifies as * internal — and every SPA router reads a `to`/`navigate()` string as a PATH. * Handing it the absolute form routes to a literal `/https://app.example.com/...` * segment. Resolving here means each product wires one line and cannot get this * wrong; the alternative is the same five lines re-derived in every product, * with only the ones whose assistant happened to emit an absolute URL finding * out they got it wrong. */ export declare function assistantRoutePath(href: string | null | undefined, baseOrigin?: string): string | null; /** * ★★ The cases where the BROWSER must win, not the router. Losing these is how * "open in new tab", "copy link" and middle-click quietly stop working — the * reason the anchor also keeps a real `href` rather than becoming a button. * * ★★ THE ANCHOR SHOWS THE RESOLVED PATH, NOT THE RAW HREF, and that is a * correctness fix rather than cosmetics. A document-relative href resolves * against the CURRENT PAGE in the browser but against the ORIGIN in * `assistantRoutePath`, so the two click modes disagreed: * * on /healthybowl/crops/42, `[x](fields)` * plain click -> navigate("/fields") (resolved at the root) * ctrl-click -> browser opens /healthybowl/crops/fields * * Same link, two destinations depending on how it was clicked — and "copy link * address" gave a third answer. Emitting `routePath` as the href makes every * mode agree, because there is then only one resolved value. A fragment and an * external URL are left exactly as written: neither is a route. */ export declare function browserShouldHandle(event: MouseEvent): boolean; /** * ★ Context, not a prop: the renderer is reached by TWO paths — the chat * transcript and the voice transcript drawer — and threading a prop through * both spines means the next surface silently ships links that reload the app. */ export declare const AssistantNavigateProvider: FC<{ value?: AssistantNavigate; children: ReactNode; }>; export declare function useAssistantNavigate(): AssistantNavigate | null; export declare const AssistantMarkdownRenderer: FC; export {}; //# sourceMappingURL=AssistantMarkdownRenderer.d.ts.map