Forked from vadimfedenko/analyze-images
Forked from vadimfedenko/analyze-images
src / strataAnalysis.ts
src / strataAnalysis.ts
import type { ToolsProviderController } from "@lmstudio/sdk";
import { candidateBytes } from "./attachmentIdentity";
import { configSchematics, globalConfigSchematics } from "./config";
export function strataAnalysisUrl(value: string): string {
const url = new URL(value.trim());
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) throw new Error("Strata analysis Base URL must be HTTP(S) without credentials, query or fragment.");
const path = url.pathname.replace(/\/+$/, "");
url.pathname = path.endsWith("/chat/completions") ? path : `${path.endsWith("/v1") ? path : `${path}/v1`}/chat/completions`;
return url.toString();
}
export async function analyzeWithStrata(ctl: ToolsProviderController, relativePath: string, prompt: string, signal: AbortSignal): Promise<string> {
signal.throwIfAborted();
const image = await candidateBytes(ctl.getWorkingDirectory(), relativePath);
return analyzeImagesWithStrata(ctl, [image], prompt, signal);
}
export async function analyzeImagesWithStrata(ctl: ToolsProviderController, images: Array<{bytes: Buffer; extension: string}>, prompt: string, signal: AbortSignal): Promise<string> {
signal.throwIfAborted();
if (!images.length || images.length > 4 || images.reduce((total, image) => total + image.bytes.length, 0) > 16 * 1024 * 1024) throw new Error("Strata image count/size limit exceeded.");
const imageContent = [];
let totalBytes = 0;
for (const image of images) {
signal.throwIfAborted();
let bytes = image.bytes;
let mime = ({ ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".webp": "image/webp" } as Record<string, string>)[image.extension];
if (!mime) { const { default: sharp } = await import("sharp"); bytes = await sharp(bytes).png().toBuffer(); mime = "image/png"; }
totalBytes += bytes.length;
if (bytes.length > 8 * 1024 * 1024 || totalBytes > 16 * 1024 * 1024) throw new Error("Converted Strata image size limit exceeded.");
imageContent.push({ type: "image_url", image_url: { url: `data:${mime};base64,${bytes.toString("base64")}` } });
}
const global = ctl.getGlobalPluginConfig(globalConfigSchematics);
const model = ctl.getPluginConfig(configSchematics).get("strataModel").trim();
if (!model) throw new Error("Set Strata analysis model.");
const key = global.get("strataApiKey").trim();
const response = await fetch(strataAnalysisUrl(global.get("strataBaseUrl")), {
method: "POST", signal, redirect: "error",
headers: { "Content-Type": "application/json", ...(key ? { Authorization: `Bearer ${key}` } : {}) },
body: JSON.stringify({ model, stream: false, reasoning_effort: "none", max_tokens: 2048,
messages: [{ role: "user", content: [
{ type: "text", text: prompt },
...imageContent,
] }],
}),
});
if (!response.ok) { await response.body?.cancel(); throw new Error(`Strata image analysis failed: HTTP ${response.status}. Check the analysis Base URL, API key and vision setup.`); }
const result = await response.json() as { error?: { message?: string }; choices?: Array<{ message?: { content?: unknown } }> };
if (result.error) throw new Error(`Strata image analysis failed: ${result.error.message ?? "unknown error"}`);
const content = result.choices?.[0]?.message?.content;
if (typeof content !== "string" || !content.trim()) throw new Error("Strata image analysis returned no final answer.");
return content.trim();
}
import type { ToolsProviderController } from "@lmstudio/sdk";
import { candidateBytes } from "./attachmentIdentity";
import { configSchematics, globalConfigSchematics } from "./config";
export function strataAnalysisUrl(value: string): string {
const url = new URL(value.trim());
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) throw new Error("Strata analysis Base URL must be HTTP(S) without credentials, query or fragment.");
const path = url.pathname.replace(/\/+$/, "");
url.pathname = path.endsWith("/chat/completions") ? path : `${path.endsWith("/v1") ? path : `${path}/v1`}/chat/completions`;
return url.toString();
}
export async function analyzeWithStrata(ctl: ToolsProviderController, relativePath: string, prompt: string, signal: AbortSignal): Promise<string> {
signal.throwIfAborted();
const image = await candidateBytes(ctl.getWorkingDirectory(), relativePath);
return analyzeImagesWithStrata(ctl, [image], prompt, signal);
}
export async function analyzeImagesWithStrata(ctl: ToolsProviderController, images: Array<{bytes: Buffer; extension: string}>, prompt: string, signal: AbortSignal): Promise<string> {
signal.throwIfAborted();
if (!images.length || images.length > 4 || images.reduce((total, image) => total + image.bytes.length, 0) > 16 * 1024 * 1024) throw new Error("Strata image count/size limit exceeded.");
const imageContent = [];
let totalBytes = 0;
for (const image of images) {
signal.throwIfAborted();
let bytes = image.bytes;
let mime = ({ ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".webp": "image/webp" } as Record<string, string>)[image.extension];
if (!mime) { const { default: sharp } = await import("sharp"); bytes = await sharp(bytes).png().toBuffer(); mime = "image/png"; }
totalBytes += bytes.length;
if (bytes.length > 8 * 1024 * 1024 || totalBytes > 16 * 1024 * 1024) throw new Error("Converted Strata image size limit exceeded.");
imageContent.push({ type: "image_url", image_url: { url: `data:${mime};base64,${bytes.toString("base64")}` } });
}
const global = ctl.getGlobalPluginConfig(globalConfigSchematics);
const model = ctl.getPluginConfig(configSchematics).get("strataModel").trim();
if (!model) throw new Error("Set Strata analysis model.");
const key = global.get("strataApiKey").trim();
const response = await fetch(strataAnalysisUrl(global.get("strataBaseUrl")), {
method: "POST", signal, redirect: "error",
headers: { "Content-Type": "application/json", ...(key ? { Authorization: `Bearer ${key}` } : {}) },
body: JSON.stringify({ model, stream: false, reasoning_effort: "none", max_tokens: 2048,
messages: [{ role: "user", content: [
{ type: "text", text: prompt },
...imageContent,
] }],
}),
});
if (!response.ok) { await response.body?.cancel(); throw new Error(`Strata image analysis failed: HTTP ${response.status}. Check the analysis Base URL, API key and vision setup.`); }
const result = await response.json() as { error?: { message?: string }; choices?: Array<{ message?: { content?: unknown } }> };
if (result.error) throw new Error(`Strata image analysis failed: ${result.error.message ?? "unknown error"}`);
const content = result.choices?.[0]?.message?.content;
if (typeof content !== "string" || !content.trim()) throw new Error("Strata image analysis returned no final answer.");
return content.trim();
}