/** Coline owns provider credentials, execution, storage and workspace charging. */ export interface ColineImageOptions { /** Id returned by ai.models(), never a provider URL. */ model: string; prompt: string; /** Reusing a key with a different request is rejected. */ idempotencyKey: string; driveId: string; referenceFileIds?: string[]; maskFileId?: string; count?: number; size?: `${number}x${number}`; aspectRatio?: `${number}:${number}`; seed?: number; quality?: "auto" | "low" | "medium" | "high"; outputFormat?: "png" | "jpeg" | "webp"; background?: "auto" | "opaque" | "transparent"; /** Maximum workspace charge approved by the caller, in millionths of USD. */ maxCostMicrousd: number; providerOptions?: Record>; partialImages?: 0 | 1 | 2 | 3; } export interface ColineAiModel { id: string; name: string; kind: "image"; supports: { edits: boolean; masks: boolean; partialImages: boolean; transparentBackground: boolean; }; /** Conservative charge per image. Configuration is controlled by Coline. */ priceMicrousd: number; } export interface ColineAiImageAsset { fileId: string; mimeType: string; } export interface ColineAiJob { id: string; kind: ColineAiKind; status: "queued" | "running" | "succeeded" | "failed" | "cancelled"; model: string; output?: ColineAiJson; images: ColineAiImageAsset[]; previews: ColineAiImageAsset[]; error: { code: string; message: string } | null; chargeMicrousd: number; createdAt: string; updatedAt: string; } export interface ColineAi { models(): Promise; run(request: ColineAiRequest): Promise; images: { generate(input: ColineImageOptions): Promise }; jobs: { events(id: string, afterSequence?: number): Promise; get(id: string): Promise; /** Summaries omit output; use get(id) for the completed result. */ list(input?: { limit?: number }): Promise; /** Cancellation stops pending work; a submitted provider request may still incur cost. */ cancel(id: string): Promise; }; } export type ColineAiKind = | "language" | "image" | "embedding" | "speech" | "transcription" | "reranking" | "video"; export type ColineAiJson = | null | boolean | number | string | ColineAiJson[] | { [key: string]: ColineAiJson }; export interface ColineAiRequestBase { model: string; idempotencyKey: string; maxCostMicrousd: number; providerOptions?: Record>; } export interface ColineTextRequest extends ColineAiRequestBase { prompt?: string; system?: string; messages?: Array<{ role: "system" | "user" | "assistant" | "tool"; content: ColineAiJson }>; maxOutputTokens?: number; temperature?: number; topP?: number; topK?: number; presencePenalty?: number; frequencyPenalty?: number; stopSequences?: string[]; seed?: number; /** JSON Schema for structured output. */ outputSchema?: Record; output?: | { type: "json" } | { type: "object"; schema: Record } | { type: "array"; element: Record } | { type: "choice"; options: string[] }; toolChoice?: "auto" | "none" | "required" | { type: "tool"; toolName: string }; webSearch?: boolean; /** Destination for files emitted by a language model. Requires files.write. */ outputDriveId?: string; /** Declared app tool names. Coline executes them through the normal app guard. */ tools?: string[]; maxSteps?: number; } export type ColineAiRequest = | { kind: "language"; input: ColineTextRequest } | { kind: "embedding"; input: ColineAiRequestBase & { values: string[]; dimensions?: number } } | { kind: "speech"; input: ColineAiRequestBase & { text: string; voice: string; driveId: string; outputFormat?: string; instructions?: string; speed?: number; language?: string; }; } | { kind: "transcription"; input: ColineAiRequestBase & { fileId: string } } | { kind: "reranking"; input: ColineAiRequestBase & { query: string; documents: string[]; topN?: number }; } | { kind: "video"; input: ColineAiRequestBase & { prompt: string; driveId: string; duration?: number; fps?: number; generateAudio?: boolean; referenceFileId?: string; frameImages?: Array<{ fileId: string; frameType: "first_frame" | "last_frame" }>; inputReferenceFileIds?: string[]; aspectRatio?: `${number}:${number}`; resolution?: `${number}x${number}`; seed?: number; count?: number; }; }; export interface ColineAiEvent { sequence: number; type: string; data: ColineAiJson; } export interface ColineAiModelInfo { id: string; name: string; kind: ColineAiKind; priceMicrousd: number; provider?: string; supports: Record; } /** Resume from a persisted sequence after reconnecting; aborting observation does not cancel the job. */ export async function* watchAiJob( ai: ColineAi, id: string, options: { signal?: AbortSignal; afterSequence?: number; intervalMs?: number } = {}, ) { let sequence = options.afterSequence ?? 0; while (!options.signal?.aborted) { const events = await ai.jobs.events(id, sequence); for (const event of events) { sequence = event.sequence; if (options.signal?.aborted) return; yield { type: "event" as const, event }; } const job = await ai.jobs.get(id); if (options.signal?.aborted) return; yield { type: "job" as const, job }; if (["succeeded", "failed", "cancelled"].includes(job.status)) { // Drain events written between the previous event read and terminal status. let remaining = await ai.jobs.events(id, sequence); while (remaining.length) { for (const event of remaining) { sequence = event.sequence; if (options.signal?.aborted) return; yield { type: "event" as const, event }; } remaining = await ai.jobs.events(id, sequence); } return; } await new Promise((resolve) => { const done = () => { clearTimeout(timer); options.signal?.removeEventListener("abort", done); resolve(); }; const timer = setTimeout(done, Math.max(100, options.intervalMs ?? 500)); options.signal?.addEventListener("abort", done, { once: true }); if (options.signal?.aborted) done(); }); } } /** Additional capabilities required by a job, shared by the host and test gate. */ export function requiredAiOperations(request: { kind: ColineAiKind; input: { model?: string | undefined; referenceFileIds?: readonly string[] | undefined; maskFileId?: string | undefined; referenceFileId?: string | undefined; frameImages?: readonly { fileId: string }[] | undefined; inputReferenceFileIds?: readonly string[] | undefined; tools?: readonly string[] | undefined; outputDriveId?: string | undefined; messages?: readonly { content: ColineAiJson }[] | undefined; }; }): Array<"files.read" | "files.write" | "tools.invoke"> { const operations = new Set<"files.read" | "files.write" | "tools.invoke">(); if (request.kind === "image" || request.kind === "speech" || request.kind === "video") operations.add("files.write"); if ( request.kind === "image" && (request.input.referenceFileIds?.length || request.input.maskFileId) ) operations.add("files.read"); if (request.kind === "transcription") operations.add("files.read"); if ( request.kind === "video" && (request.input.referenceFileId || request.input.frameImages?.length || request.input.inputReferenceFileIds?.length) ) operations.add("files.read"); if (request.kind === "language") { if (request.input.tools?.length) operations.add("tools.invoke"); if (request.input.outputDriveId) operations.add("files.write"); for (const message of request.input.messages ?? []) if (Array.isArray(message.content)) for (const part of message.content) if ( part && typeof part === "object" && !Array.isArray(part) && typeof part.fileId === "string" ) operations.add("files.read"); } return [...operations]; }