import { type AiEnv, type AiImage, type AiProviderName } from './providers.js'; /** * The two things AI assist does, and the flag that decides whether to offer them. * * ## `available` is the mailer's `delivers` again * * A Generate button that 500s because nobody set a key is the same failure as a forgot-password form * whose success message is a lie: the screen made a promise the deployment cannot keep. So every * affordance gates on `available`, which is true only when a provider is chosen, its key is in the * environment, and the feature's own toggle is on. Three conditions, one boolean, checked in the * template rather than discovered on submit. * * ## Nothing here writes to a row * * Both functions return a string for a human to accept. That is the rule the three-state alt-text * model forces: a machine writing `''` would mark an image **decorative**, which is a claim that it * carries no information and which makes a screen reader skip it. No generator can know that, and no * amount of prompt care makes an empty completion safe to store. The same reasoning is milder but * real for a meta description — it is a claim about what a page is *for*, and the person who wrote * the page is the one who can judge it. */ /** What an editor is told when the feature is off, so a screen never has to word this itself. */ export declare const AI_UNAVAILABLE: string; export interface AiSettings { provider: string | null; model: string | null; altText: boolean; seo: boolean; } export interface Assistant { /** Which provider would be called, and with which model. Reported on Settings → System. */ readonly provider: AiProviderName | null; readonly model: string | null; /** A provider is chosen and its key exists. Neither feature is offered without this. */ readonly configured: boolean; /** `configured` **and** this feature's own toggle. What a template gates on. */ readonly altText: boolean; readonly seo: boolean; /** * A sentence describing one image, for an editor to accept or rewrite. * * Takes **bytes**, never a URL. The asset may live in R2 behind a Worker route, and a provider * fetching `TAPROOT_MEDIA_URL` from its own network is a request that can fail for reasons this * deployment cannot see — a private bucket, a host that only answers inside the zone. Reading * through the storage adapter is the one path known to work wherever the CMS runs. */ describeImage(image: AiImage, context: AltTextContext): Promise; /** A meta title and description proposed from the page's own prose. */ suggestSeo(input: SeoContext): Promise<{ title: string; description: string; }>; } export interface AltTextContext { /** The filename, which is often the only clue about intent — `dean-portrait-2027.jpg`. */ filename: string; /** The page or item the image sits on, when the caller knows it. Alt text is context-dependent. */ usedOn?: string | null; } export interface SeoContext { title: string; /** * The item's prose, already flattened and capped by the caller. * * The caller passes `content_item_text`'s row — the same flattened text the search index holds, so * what the model reads is what the page says, including prose inside blocks and repeater rows. It * is capped rather than sent whole: a long page is thousands of tokens on every press of a button, * and a meta description is drawn from the opening far more than the tail. */ text: string; } /** * Build the assistant from the environment and the settings row. * * Both halves are needed and neither is sufficient: the key says the deployment *can* call a * provider, the settings row says an operator *chose* to. Deriving the provider from whichever key * happens to be present would pick for them when several are set, and would make "a key is * configured but the feature is off" — a perfectly reasonable state — unsayable. */ export declare function resolveAssistant(env: AiEnv, settings: AiSettings): Assistant;