delete: images()

This commit is contained in:
Cristina Poncela Cubeiro
2026-05-07 16:21:50 +02:00
parent 074747c687
commit 32b2cd0720
9 changed files with 119 additions and 309 deletions

View File

@@ -16,7 +16,6 @@ import type {
ImagesOptions,
TextContent,
} from "../../types.js";
import { AssistantImagesEventStream } from "../../utils/event-stream.js";
import { headersToRecord } from "../../utils/headers.js";
import { sanitizeSurrogates } from "../../utils/sanitize-unicode.js";
@@ -36,87 +35,70 @@ type OpenRouterImageGenerationResponse = ChatCompletion & {
choices: OpenRouterImageGenerationChoice[];
};
export const imagesOpenRouter: ImagesFunction<"openrouter-images", ImagesOptions> = (
export const generateImagesOpenRouter: ImagesFunction<"openrouter-images", ImagesOptions> = async (
model: ImagesModel<"openrouter-images">,
context: ImagesContext,
options?: ImagesOptions,
) => {
const stream = new AssistantImagesEventStream();
const output: AssistantImages = {
api: model.api,
provider: model.provider,
model: model.id,
output: [],
stopReason: "stop",
timestamp: Date.now(),
};
(async () => {
const output: AssistantImages = {
api: model.api,
provider: model.provider,
model: model.id,
output: [],
stopReason: "stop",
timestamp: Date.now(),
};
try {
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
const client = createClient(model, apiKey, options?.headers);
let params = buildParams(model, context);
const nextParams = await options?.onPayload?.(params, model);
if (nextParams !== undefined) {
params = nextParams as typeof params;
}
const requestOptions = {
...(options?.signal ? { signal: options.signal } : {}),
...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),
...(options?.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}),
};
const { data: response, response: rawResponse } = await client.chat.completions
.create(params as unknown as ChatCompletionCreateParamsNonStreaming, requestOptions)
.withResponse();
await options?.onResponse?.(
{ status: rawResponse.status, headers: headersToRecord(rawResponse.headers) },
model,
);
stream.push({ type: "start", partial: output });
const imageResponse = response as OpenRouterImageGenerationResponse;
output.responseId = imageResponse.id;
if (imageResponse.usage) {
output.usage = parseUsage(imageResponse.usage, model);
}
const choice = imageResponse.choices[0];
if (choice) {
const content = choice.message.content;
if (typeof content === "string" && content.length > 0) {
output.output.push({ type: "text", text: content } satisfies TextContent);
}
for (const image of choice.message.images ?? []) {
const imageUrl = typeof image.image_url === "string" ? image.image_url : image.image_url?.url;
if (!imageUrl?.startsWith("data:")) continue;
const matches = imageUrl.match(/^data:([^;]+);base64,(.+)$/);
if (!matches) continue;
const imageBlock: ImageContent = {
type: "image",
mimeType: matches[1],
data: matches[2],
};
output.output.push(imageBlock);
const contentIndex = output.output.length - 1;
stream.push({ type: "image_start", contentIndex, partial: output });
stream.push({ type: "image_end", contentIndex, image: imageBlock, partial: output });
}
}
stream.push({ type: "done", reason: "stop", images: output });
stream.end();
} catch (error) {
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
stream.push({ type: "error", reason: output.stopReason, error: output });
stream.end(output);
try {
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
const client = createClient(model, apiKey, options?.headers);
let params = buildParams(model, context);
const nextParams = await options?.onPayload?.(params, model);
if (nextParams !== undefined) {
params = nextParams as typeof params;
}
})();
const requestOptions = {
...(options?.signal ? { signal: options.signal } : {}),
...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),
...(options?.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}),
};
const { data: response, response: rawResponse } = await client.chat.completions
.create(params as unknown as ChatCompletionCreateParamsNonStreaming, requestOptions)
.withResponse();
await options?.onResponse?.({ status: rawResponse.status, headers: headersToRecord(rawResponse.headers) }, model);
return stream;
const imageResponse = response as OpenRouterImageGenerationResponse;
output.responseId = imageResponse.id;
if (imageResponse.usage) {
output.usage = parseUsage(imageResponse.usage, model);
}
const choice = imageResponse.choices[0];
if (choice) {
const content = choice.message.content;
if (typeof content === "string" && content.length > 0) {
output.output.push({ type: "text", text: content } satisfies TextContent);
}
for (const image of choice.message.images ?? []) {
const imageUrl = typeof image.image_url === "string" ? image.image_url : image.image_url?.url;
if (!imageUrl?.startsWith("data:")) continue;
const matches = imageUrl.match(/^data:([^;]+);base64,(.+)$/);
if (!matches) continue;
output.output.push({
type: "image",
mimeType: matches[1],
data: matches[2],
} satisfies ImageContent);
}
}
return output;
} catch (error) {
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
return output;
}
};
function createClient(

View File

@@ -1,74 +1,10 @@
import { registerImagesApiProvider } from "../../images-api-registry.js";
import type {
AssistantImages,
AssistantImagesEvent,
ImagesContext,
ImagesFunction,
ImagesModel,
ImagesOptions,
} from "../../types.js";
import { AssistantImagesEventStream } from "../../utils/event-stream.js";
import type { imagesOpenRouter as imagesOpenRouterFunction } from "./openrouter.js";
interface OpenRouterImagesProviderModule {
imagesOpenRouter: typeof imagesOpenRouterFunction;
}
let openRouterImagesProviderModulePromise: Promise<OpenRouterImagesProviderModule> | undefined;
function forwardImagesStream(target: AssistantImagesEventStream, source: AsyncIterable<AssistantImagesEvent>): void {
(async () => {
for await (const event of source) {
target.push(event);
}
target.end();
})();
}
function createLazyLoadErrorImages(model: ImagesModel<"openrouter-images">, error: unknown): AssistantImages {
return {
api: model.api,
provider: model.provider,
model: model.id,
output: [],
stopReason: "error",
errorMessage: error instanceof Error ? error.message : String(error),
timestamp: Date.now(),
};
}
function loadOpenRouterImagesProviderModule(): Promise<OpenRouterImagesProviderModule> {
openRouterImagesProviderModulePromise ||= import("./openrouter.js").then(
(module) => module as OpenRouterImagesProviderModule,
);
return openRouterImagesProviderModulePromise;
}
export const imagesOpenRouter: ImagesFunction<"openrouter-images", ImagesOptions> = (
model: ImagesModel<"openrouter-images">,
context: ImagesContext,
options?: ImagesOptions,
) => {
const outer = new AssistantImagesEventStream();
loadOpenRouterImagesProviderModule()
.then((module) => {
const inner = module.imagesOpenRouter(model, context, options);
forwardImagesStream(outer, inner);
})
.catch((error) => {
const images = createLazyLoadErrorImages(model, error);
outer.push({ type: "error", reason: "error", error: images });
outer.end(images);
});
return outer;
};
import { generateImagesOpenRouter } from "./openrouter.js";
export function registerBuiltInImagesApiProviders(): void {
registerImagesApiProvider({
api: "openrouter-images",
images: imagesOpenRouter,
generateImages: generateImagesOpenRouter,
});
}