Repository Public
1import type { z } from "zod";23/** Provider-neutral interfaces. Application code depends on these, never on a vendor SDK. */45export type CostSource = "ticks" | "computed" | "unknown";6export interface Usage { costUsd: number | null; source: CostSource; inputTokens?: number; outputTokens?: number }78export class ProviderError extends Error {9 constructor(message: string, readonly opts: { status?: number; code?: string; retryable: boolean; billed?: boolean } = { retryable: false }) {10 super(message);11 this.name = "ProviderError";12 }13 get retryable() { return this.opts.retryable; }14}1516export interface LLMRequest<T extends z.ZodType> {17 name: string;18 schema: T;19 system: string;20 user: string;21 /** JPEG/PNG images for multimodal critique. */22 images?: { bytes: Buffer; mime: string }[];23 effort?: "none" | "low" | "medium" | "high" | "xhigh";24 model?: string;25}26export interface LLMResult<T> { data: T; usage: Usage; model: string }27export interface LLMProvider {28 generateStructured<T extends z.ZodType>(req: LLMRequest<T>): Promise<LLMResult<z.infer<T>>>;29 /** Same call shape; kept separate so a different (vision) model can be routed. */30 critique<T extends z.ZodType>(req: LLMRequest<T>): Promise<LLMResult<z.infer<T>>>;31 /** Structured answer grounded by web search; `citations` are the URLs the provider reports. */32 research<T extends z.ZodType>(req: LLMRequest<T>): Promise<LLMResult<z.infer<T>> & { citations: string[] }>;33}3435export type AspectRatio = "9:16" | "1:1" | "16:9";36export interface ImageRequest { prompt: string; aspectRatio: AspectRatio; resolution?: "1k" | "1.5k" | "2k"; quality?: "low" | "medium"; model?: string }37export interface ImageResult { bytes: Buffer; mime: string; usage: Usage; model: string }38export interface ImageProvider {39 generate(req: ImageRequest): Promise<ImageResult>;40 edit(req: ImageRequest & { image: { bytes: Buffer; mime: string } }): Promise<ImageResult>;41}4243export type VideoResolution = "480p" | "720p" | "1080p";44export interface VideoRequest {45 model: string;46 prompt: string;47 duration: number;48 resolution: VideoResolution;49 aspectRatio?: AspectRatio;50 image?: { bytes: Buffer; mime: string };51 referenceImages?: { bytes: Buffer; mime: string }[];52}53export interface VideoStatus {54 status: "pending" | "done" | "failed";55 progress: number | null;56 url?: string;57 duration?: number;58 moderationOk?: boolean;59 usage: Usage;60 error?: { code: string; message: string };61}62export interface VideoProvider {63 /** Submits and returns the provider request id: persist it before polling. */64 submit(req: VideoRequest): Promise<{ requestId: string }>;65 poll(requestId: string): Promise<VideoStatus>;66 download(url: string): Promise<Buffer>;67 textToVideo(req: Omit<VideoRequest, "image" | "referenceImages">): Promise<{ requestId: string }>;68 imageToVideo(req: VideoRequest & { image: { bytes: Buffer; mime: string } }): Promise<{ requestId: string }>;69 referenceToVideo(req: VideoRequest & { referenceImages: { bytes: Buffer; mime: string }[] }): Promise<{ requestId: string }>;70 editVideo?(req: { model: string; prompt: string; videoUrl: string }): Promise<{ requestId: string }>;71}7273export interface SpeechRequest { text: string; voiceId: string; language: "en" | "fr"; speed?: number }74export interface CharTiming { char: string; start: number; end: number }75export interface SpeechResult { bytes: Buffer; mime: string; duration: number; chars: CharTiming[]; usage: Usage }76export interface SpeechProvider {77 synthesize(req: SpeechRequest): Promise<SpeechResult>;78 voices(): Promise<{ id: string; name: string; gender: string }[]>;79}80