Files
atelier/sidecar/providers/types.ts
T
pastilhas f23ec1283b manifest, thin proxy router, and the sidecar backend
ported from the standalone officer-atelier service: providers (adapter-shaped, openai the only
adapter today), the generation queue, collections, and the database layer — kept on its own
Postgres database rather than folded into officer_db, since both collections and provider_settings
had rows on the machine this was extracted from.

what changed rather than just moved:
- images are served from an authenticated GET /files/:filename instead of an unauthenticated
  PUBLIC_URL-prefixed static route
- the OpenAI-key bootstrap fallback reads the secret store (getSecret) before .env, per-adapter
- provider_settings (the one-row-per-adapter shape providers replaced) did not come along — it has
  no callers left anywhere in the server code, only a migration script that also did not move

ping/echo (smoke-test routes with no caller) did not move either; the sidecar's own /_health covers
liveness.

not yet wired into the platform: sidecar/index.ts exists but nothing mounts it, and web/ has not
been written, so this is not yet a discoverable plugin.
2026-08-20 03:53:53 +00:00

107 lines
3.9 KiB
TypeScript

/**
* The request Atelier accepts, which is OpenAI's `POST /v1/images/generations` body verbatim.
*
* Kept as their vocabulary rather than a translated one of our own: a caller that already knows the
* OpenAI API knows this, and a provider added later is the one that has to do the translating —
* which is where the knowledge of that provider already lives.
*/
export type ImageRequest = {
prompt: string;
model?: string;
n?: number;
quality?: 'auto' | 'high' | 'medium' | 'low' | 'hd' | 'standard';
size?: string;
response_format?: 'url' | 'b64_json';
output_format?: 'png' | 'jpeg' | 'webp';
output_compression?: number;
background?: 'transparent' | 'opaque' | 'auto';
moderation?: 'low' | 'auto';
style?: 'vivid' | 'natural';
stream?: boolean;
partial_images?: number;
user?: string;
};
/**
* OpenAI's `POST /v1/images/edits` body, minus the files themselves.
*
* A separate type rather than flags on ImageRequest, because the parameter sets genuinely differ:
* edits take `input_fidelity` and no `style` or `moderation`, its `quality` has no `hd`, and its
* model default is gpt-image-1.5 where generations default to dall-e-2.
*/
export type ImageEditRequest = {
prompt: string;
model?: string;
n?: number;
quality?: 'auto' | 'high' | 'medium' | 'low' | 'standard';
size?: string;
response_format?: 'url' | 'b64_json';
output_format?: 'png' | 'jpeg' | 'webp';
output_compression?: number;
background?: 'transparent' | 'opaque' | 'auto';
input_fidelity?: 'high' | 'low';
stream?: boolean;
partial_images?: number;
user?: string;
};
/** An uploaded file on its way to a provider. */
export type UploadedImage = { filename: string; bytes: Uint8Array; type: string };
/** OpenAI's usage object. Stored raw so a cost computed with the wrong price can be recomputed. */
export type ImageUsage = {
total_tokens?: number;
input_tokens?: number;
output_tokens?: number;
input_tokens_details?: { text_tokens?: number; image_tokens?: number };
};
/** One generated image, as the provider returned it. */
export type ProviderImage = {
/** Raw bytes. Providers hand back base64; decoding is theirs to do, so the queue only writes files. */
bytes: Uint8Array;
/** png / jpeg / webp — decides the extension on disk and the served content type. */
format: string;
revisedPrompt?: string;
};
export type ProviderResult = {
images: ProviderImage[];
usage?: ImageUsage;
/** Echoed back by the provider: the size, quality and background actually used. */
echoed: Record<string, unknown>;
};
/**
* An adapter: how to talk to one vendor's image API.
*
* Deliberately holds no key, no chosen model and no enabled flag. Those live in a `providers` row,
* because they are decisions rather than knowledge, and several rows share one adapter.
*/
export type ImageAdapter = {
readonly name: string;
/** For display — "OpenAI" rather than "openai", used to name providers built from it. */
readonly label: string;
/** Models this adapter is known to serve. Advisory — an unknown model is passed through. */
readonly models: readonly string[];
readonly defaultModel: string;
/**
* The key is passed in rather than read here: it can come from the database or the environment, and
* which one is a question about configuration, not about how to talk to this provider.
*/
generate(request: ImageRequest, apiKey: string, signal?: AbortSignal): Promise<ProviderResult>;
/**
* Edit. Optional: a provider that cannot do it says so by not having it, and the route answers 400
* rather than the queue discovering it after the row is written.
*/
edit?(
request: ImageEditRequest,
images: UploadedImage[],
mask: UploadedImage | undefined,
apiKey: string,
signal?: AbortSignal,
): Promise<ProviderResult>;
/** USD for the reported usage, or null when this model's price is not known. */
costUsd(model: string, usage: ImageUsage | undefined): string | null;
};