export * as Image from "./image" import { makeLocationNode } from "./effect/app-node" import { Context, Effect, Layer, Schema } from "effect" import { Config } from "./config" import { FileSystem } from "./filesystem" export class ResizerUnavailableError extends Schema.TaggedErrorClass()( "Image.ResizerUnavailableError", {}, ) {} export class DecodeError extends Schema.TaggedErrorClass()("Image.DecodeError", { resource: Schema.String, }) { override get message() { return `Image could not be decoded: ${this.resource}` } } export class SizeError extends Schema.TaggedErrorClass()("Image.SizeError", { resource: Schema.String, width: Schema.Number, height: Schema.Number, bytes: Schema.Number, maxWidth: Schema.Number, maxHeight: Schema.Number, maxBytes: Schema.Number, }) { override get message() { return `Image ${this.resource} is ${this.width}x${this.height} with base64 size ${this.bytes}, exceeding configured limits ${this.maxWidth}x${this.maxHeight}/${this.maxBytes} bytes` } } export interface Interface { readonly normalize: ( resource: string, content: FileSystem.Content & { readonly encoding: "base64" }, ) => Effect.Effect< FileSystem.Content & { readonly encoding: "base64" }, ResizerUnavailableError | DecodeError | SizeError > } export class Service extends Context.Service()("@opencode/Image") {} const layer = Layer.effect( Service, Effect.gen(function* () { const config = yield* Config.Service const loadAdapter = yield* Effect.cached( Effect.tryPromise({ try: () => import("./image/photon"), catch: () => new ResizerUnavailableError(), }).pipe(Effect.flatMap((adapter) => adapter.make)), ) const normalize = Effect.fn("Image.normalize")(function* ( resource: string, content: FileSystem.Content & { readonly encoding: "base64" }, ) { const image = Object.assign( {}, ...(yield* config.entries()).flatMap((entry) => entry.type === "document" && entry.info.attachments?.image ? [entry.info.attachments.image] : [], ), ) const normalize = yield* loadAdapter return yield* normalize(resource, content, { autoResize: image.auto_resize ?? true, maxWidth: image.max_width ?? 2_000, maxHeight: image.max_height ?? 2_000, maxBase64Bytes: image.max_base64_bytes ?? 5 * 1024 * 1024, }) }) return Service.of({ normalize }) }), ) export const locationLayer = layer.pipe(Layer.provide(Config.locationLayer)) export const node = makeLocationNode({ service: Service, layer, deps: [Config.node] })