From 8ede1cc801574275c28fca55e883ff12647ae9a7 Mon Sep 17 00:00:00 2001 From: Xiangzhe Date: Thu, 13 Aug 2026 15:40:41 -0300 Subject: [PATCH] feat(ocr): transformation layer on ocrRegistry (Mistral shape canonical) --- open-sse/config/ocrRegistry.ts | 39 +++++++++++++++++++ .../unit/ocr-registry-transformations.test.ts | 28 +++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 tests/unit/ocr-registry-transformations.test.ts diff --git a/open-sse/config/ocrRegistry.ts b/open-sse/config/ocrRegistry.ts index fdf47d44f1..5bf27a557f 100644 --- a/open-sse/config/ocrRegistry.ts +++ b/open-sse/config/ocrRegistry.ts @@ -16,6 +16,7 @@ export interface OcrProvider { authType: string; authHeader: string; models: OcrModel[]; + transformation?: OcrTransformation; } export interface ParsedOcrModel { @@ -23,6 +24,44 @@ export interface ParsedOcrModel { model: string | null; } +export interface OcrResponseShape { + pages: Array<{ index: number; markdown: string }>; + model: string; + usage_info?: Record; +} + +export interface OcrTransformation { + buildRequest(args: { + baseUrl: string; + token: string; + body: Record; + modelId: string; + }): { url: string; init: RequestInit }; + parseResponse(raw: unknown): OcrResponseShape; + /** Async providers (Azure DI): return the poll URL from the first response, else null. */ + pollUrl?(res: Response): string | null; +} + +export const MISTRAL_PASSTHROUGH: OcrTransformation = { + buildRequest({ baseUrl, token, body, modelId }) { + return { + url: baseUrl, + init: { + method: "POST", + headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, + body: JSON.stringify({ ...body, model: modelId }), + }, + }; + }, + parseResponse(raw) { + return raw as OcrResponseShape; + }, +}; + +export function getOcrTransformation(providerId: string): OcrTransformation { + return OCR_PROVIDERS[providerId]?.transformation ?? MISTRAL_PASSTHROUGH; +} + export const OCR_PROVIDERS: Record = { mistral: { id: "mistral", diff --git a/tests/unit/ocr-registry-transformations.test.ts b/tests/unit/ocr-registry-transformations.test.ts new file mode 100644 index 0000000000..df25a1fc05 --- /dev/null +++ b/tests/unit/ocr-registry-transformations.test.ts @@ -0,0 +1,28 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { + OCR_PROVIDERS, + getOcrTransformation, + MISTRAL_PASSTHROUGH, +} from "../../open-sse/config/ocrRegistry.ts"; + +test("mistral resolves the passthrough transformation by default", () => { + const t = getOcrTransformation("mistral"); + assert.equal(t, MISTRAL_PASSTHROUGH); + const { url, init } = t.buildRequest({ + baseUrl: OCR_PROVIDERS.mistral.baseUrl, + token: "sk-test", + body: { document: { type: "image_url", image_url: "https://x/y.png" } }, + modelId: "mistral-ocr-latest", + }); + assert.equal(url, "https://api.mistral.ai/v1/ocr"); + assert.equal(init.method, "POST"); + assert.equal((init.headers as Record).Authorization, "Bearer sk-test"); + const sent = JSON.parse(String(init.body)); + assert.equal(sent.model, "mistral-ocr-latest"); +}); + +test("passthrough parseResponse returns the body unchanged (Mistral is the canonical shape)", () => { + const raw = { pages: [{ index: 0, markdown: "hello" }], model: "mistral-ocr-latest" }; + assert.deepEqual(MISTRAL_PASSTHROUGH.parseResponse(raw), raw); +});