improve path resolve logic

This commit is contained in:
Peter Steinberger 2025-06-08 03:49:04 +01:00
parent 8145455fc4
commit 3ce1d38050

View file

@ -8,11 +8,9 @@ import {
import { executeSwiftCli, readImageAsBase64 } from "../utils/peekaboo-cli.js"; import { executeSwiftCli, readImageAsBase64 } from "../utils/peekaboo-cli.js";
import { performAutomaticAnalysis } from "../utils/image-analysis.js"; import { performAutomaticAnalysis } from "../utils/image-analysis.js";
import { buildImageSummary } from "../utils/image-summary.js"; import { buildImageSummary } from "../utils/image-summary.js";
import { buildSwiftCliArgs } from "../utils/image-cli-args.js"; import { buildSwiftCliArgs, resolveImagePath } from "../utils/image-cli-args.js";
import { parseAIProviders } from "../utils/ai-providers.js"; import { parseAIProviders } from "../utils/ai-providers.js";
import * as fs from "fs/promises"; import * as fs from "fs/promises";
import * as pathModule from "path";
import * as os from "os";
export { imageToolSchema } from "../types/index.js"; export { imageToolSchema } from "../types/index.js";
@ -21,7 +19,7 @@ export async function imageToolHandler(
context: ToolContext, context: ToolContext,
): Promise<ToolResponse> { ): Promise<ToolResponse> {
const { logger } = context; const { logger } = context;
let tempImagePathUsed: string | undefined = undefined; let tempDirUsed: string | undefined = undefined;
let finalSavedFiles: SavedFile[] = []; let finalSavedFiles: SavedFile[] = [];
let analysisAttempted = false; let analysisAttempted = false;
let analysisSucceeded = false; let analysisSucceeded = false;
@ -32,28 +30,13 @@ export async function imageToolHandler(
logger.debug({ input }, "Processing peekaboo.image tool call"); logger.debug({ input }, "Processing peekaboo.image tool call");
// Determine effective path and format for Swift CLI // Determine effective path and format for Swift CLI
let effectivePath = input.path;
const swiftFormat = input.format === "data" ? "png" : (input.format || "png"); const swiftFormat = input.format === "data" ? "png" : (input.format || "png");
// If no path is provided by the user, we MUST use a temporary path for the Swift CLI to write to. // Resolve the effective path using the centralized logic
const needsTempPath = !input.path; const { effectivePath, tempDirUsed: tempDir } = await resolveImagePath(input, logger);
tempDirUsed = tempDir;
if (needsTempPath) { const args = buildSwiftCliArgs(input, effectivePath, swiftFormat, logger);
const tempDir = await fs.mkdtemp(
pathModule.join(os.tmpdir(), "peekaboo-img-"),
);
tempImagePathUsed = pathModule.join(
tempDir,
`capture.${swiftFormat}`,
);
effectivePath = tempImagePathUsed;
logger.debug(
{ tempPath: tempImagePathUsed },
"Using temporary path for capture.",
);
}
const args = buildSwiftCliArgs(input, logger, effectivePath, swiftFormat);
const swiftResponse = await executeSwiftCli(args, logger); const swiftResponse = await executeSwiftCli(args, logger);
@ -102,35 +85,20 @@ export async function imageToolHandler(
} }
const captureData = imageData; const captureData = imageData;
const imagePathForAnalysis = captureData.saved_files[0].path;
// Determine which files to report as saved // Determine which files to report as saved
if (input.question && tempImagePathUsed) { if (tempDirUsed) {
// Analysis with temp path - don't include in saved_files // Temporary directory was used - don't include in saved_files
finalSavedFiles = [];
} else if (!input.path && (input.format === "data" || !input.format)) {
// Data format without path - don't include in saved_files
finalSavedFiles = []; finalSavedFiles = [];
} else { } else {
// User provided path or default save behavior - include in saved_files // User provided path or default save behavior - include in saved_files
finalSavedFiles = captureData.saved_files || []; finalSavedFiles = captureData.saved_files || [];
} }
let imageBase64ForAnalysis: string | undefined;
if (input.question) { if (input.question) {
analysisAttempted = true; analysisAttempted = true;
try { const analysisResults: Array<{ label: string; text: string }> = [];
imageBase64ForAnalysis = await readImageAsBase64(imagePathForAnalysis);
logger.debug("Image read successfully for analysis.");
} catch (readError) {
logger.error(
{ error: readError, path: imagePathForAnalysis },
"Failed to read captured image for analysis",
);
analysisText = `Analysis skipped: Failed to read captured image at ${imagePathForAnalysis}. Error: ${readError instanceof Error ? readError.message : "Unknown read error"}`;
}
if (imageBase64ForAnalysis) {
const configuredProviders = parseAIProviders( const configuredProviders = parseAIProviders(
process.env.PEEKABOO_AI_PROVIDERS || "", process.env.PEEKABOO_AI_PROVIDERS || "",
); );
@ -139,21 +107,52 @@ export async function imageToolHandler(
"Analysis skipped: AI analysis not configured on this server (PEEKABOO_AI_PROVIDERS is not set or empty)."; "Analysis skipped: AI analysis not configured on this server (PEEKABOO_AI_PROVIDERS is not set or empty).";
logger.warn(analysisText); logger.warn(analysisText);
} else { } else {
// Iterate through all saved files for analysis
for (const savedFile of captureData.saved_files) {
try {
const imageBase64 = await readImageAsBase64(savedFile.path);
logger.debug({ path: savedFile.path }, "Image read successfully for analysis.");
const analysisResult = await performAutomaticAnalysis( const analysisResult = await performAutomaticAnalysis(
imageBase64ForAnalysis, imageBase64,
input.question, input.question,
logger, logger,
process.env.PEEKABOO_AI_PROVIDERS || "", process.env.PEEKABOO_AI_PROVIDERS || "",
); );
if (analysisResult.error) { if (analysisResult.error) {
analysisText = analysisResult.error; analysisResults.push({
label: savedFile.item_label || "Unknown",
text: analysisResult.error,
});
} else { } else {
analysisText = analysisResult.analysisText; analysisResults.push({
label: savedFile.item_label || "Unknown",
text: analysisResult.analysisText || "",
});
modelUsed = analysisResult.modelUsed; modelUsed = analysisResult.modelUsed;
analysisSucceeded = true; analysisSucceeded = true;
logger.info({ provider: modelUsed }, "Image analysis successful"); logger.info({ provider: modelUsed, path: savedFile.path }, "Image analysis successful");
} }
} catch (readError) {
logger.error(
{ error: readError, path: savedFile.path },
"Failed to read captured image for analysis",
);
analysisResults.push({
label: savedFile.item_label || "Unknown",
text: `Analysis skipped: Failed to read captured image at ${savedFile.path}. Error: ${readError instanceof Error ? readError.message : "Unknown read error"}`,
});
}
}
// Format the analysis results
if (analysisResults.length === 1) {
analysisText = analysisResults[0].text;
} else if (analysisResults.length > 1) {
analysisText = analysisResults
.map(result => `Analysis for ${result.label}:\n${result.text}`)
.join("\n\n");
} }
} }
} }
@ -169,7 +168,9 @@ export async function imageToolHandler(
content.push({ type: "text", text: `Analysis Result: ${analysisText}` }); content.push({ type: "text", text: `Analysis Result: ${analysisText}` });
} }
// Return base64 data if format is 'data' or path not provided (and no question) // Return base64 data if:
// 1. Format is explicitly 'data', OR
// 2. No path was provided AND no question is asked
const shouldReturnData = (input.format === "data" || !input.path) && !input.question; const shouldReturnData = (input.format === "data" || !input.path) && !input.question;
if (shouldReturnData && captureData.saved_files?.length > 0) { if (shouldReturnData && captureData.saved_files?.length > 0) {
@ -231,23 +232,22 @@ export async function imageToolHandler(
_meta: { backend_error_code: "UNEXPECTED_HANDLER_ERROR" }, _meta: { backend_error_code: "UNEXPECTED_HANDLER_ERROR" },
}; };
} finally { } finally {
if (tempImagePathUsed) { // If we used a temporary directory, we need to clean up all files inside it and the directory itself.
if (tempDirUsed) {
logger.debug( logger.debug(
{ tempPath: tempImagePathUsed }, { tempDir: tempDirUsed },
"Attempting to delete temporary image file.", "Attempting to delete temporary capture directory.",
); );
try { try {
await fs.unlink(tempImagePathUsed); await fs.rm(tempDirUsed, { recursive: true, force: true });
const tempDir = pathModule.dirname(tempImagePathUsed);
await fs.rmdir(tempDir);
logger.info( logger.info(
{ tempPath: tempImagePathUsed }, { tempDir: tempDirUsed },
"Temporary image file and directory deleted.", "Temporary capture directory deleted.",
); );
} catch (cleanupError) { } catch (cleanupError) {
logger.warn( logger.warn(
{ error: cleanupError, path: tempImagePathUsed }, { error: cleanupError, path: tempDirUsed },
"Failed to delete temporary image file or directory.", "Failed to delete temporary capture directory.",
); );
} }
} }