diff --git a/src/media-understanding/output-extract.ts b/src/media-understanding/output-extract.ts new file mode 100644 index 0000000000..e8bf57c951 --- /dev/null +++ b/src/media-understanding/output-extract.ts @@ -0,0 +1,26 @@ +export function extractLastJsonObject(raw: string): unknown { + const trimmed = raw.trim(); + const start = trimmed.lastIndexOf("{"); + if (start === -1) { + return null; + } + const slice = trimmed.slice(start); + try { + return JSON.parse(slice); + } catch { + return null; + } +} + +export function extractGeminiResponse(raw: string): string | null { + const payload = extractLastJsonObject(raw); + if (!payload || typeof payload !== "object") { + return null; + } + const response = (payload as { response?: unknown }).response; + if (typeof response !== "string") { + return null; + } + const trimmed = response.trim(); + return trimmed || null; +} diff --git a/src/media-understanding/runner.entries.ts b/src/media-understanding/runner.entries.ts index 8ef338c412..b40e16fa53 100644 --- a/src/media-understanding/runner.entries.ts +++ b/src/media-understanding/runner.entries.ts @@ -25,6 +25,7 @@ import { DEFAULT_TIMEOUT_SECONDS, } from "./defaults.js"; import { MediaUnderstandingSkipError } from "./errors.js"; +import { extractGeminiResponse } from "./output-extract.js"; import { describeImageWithModel } from "./providers/image.js"; import { getMediaUnderstandingProvider, normalizeMediaProviderId } from "./providers/index.js"; import { resolveMaxBytes, resolveMaxChars, resolvePrompt, resolveTimeoutMs } from "./resolve.js"; @@ -40,33 +41,6 @@ function trimOutput(text: string, maxChars?: number): string { return trimmed.slice(0, maxChars).trim(); } -function extractLastJsonObject(raw: string): unknown { - const trimmed = raw.trim(); - const start = trimmed.lastIndexOf("{"); - if (start === -1) { - return null; - } - const slice = trimmed.slice(start); - try { - return JSON.parse(slice); - } catch { - return null; - } -} - -function extractGeminiResponse(raw: string): string | null { - const payload = extractLastJsonObject(raw); - if (!payload || typeof payload !== "object") { - return null; - } - const response = (payload as { response?: unknown }).response; - if (typeof response !== "string") { - return null; - } - const trimmed = response.trim(); - return trimmed || null; -} - function extractSherpaOnnxText(raw: string): string | null { const tryParse = (value: string): string | null => { const trimmed = value.trim(); diff --git a/src/media-understanding/runner.ts b/src/media-understanding/runner.ts index e0590c9817..8dd7e2f979 100644 --- a/src/media-understanding/runner.ts +++ b/src/media-understanding/runner.ts @@ -32,6 +32,7 @@ import { DEFAULT_IMAGE_MODELS, } from "./defaults.js"; import { isMediaUnderstandingSkipError } from "./errors.js"; +import { extractGeminiResponse } from "./output-extract.js"; import { buildMediaUnderstandingRegistry, getMediaUnderstandingProvider, @@ -186,33 +187,6 @@ async function fileExists(filePath?: string | null): Promise { } } -function extractLastJsonObject(raw: string): unknown { - const trimmed = raw.trim(); - const start = trimmed.lastIndexOf("{"); - if (start === -1) { - return null; - } - const slice = trimmed.slice(start); - try { - return JSON.parse(slice); - } catch { - return null; - } -} - -function extractGeminiResponse(raw: string): string | null { - const payload = extractLastJsonObject(raw); - if (!payload || typeof payload !== "object") { - return null; - } - const response = (payload as { response?: unknown }).response; - if (typeof response !== "string") { - return null; - } - const trimmed = response.trim(); - return trimmed || null; -} - async function probeGeminiCli(): Promise { const cached = geminiProbeCache.get("gemini"); if (cached) {