refactor(look-at): split tools.ts to comply with 200 LOC module rule
Extract input preparation and image conversion handling into look-at-input-preparer.ts. Extract prompt construction and multimodal session execution into look-at-prompt.ts and look-at-session-runner.ts while keeping createLookAt stable. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
import { basename } from "node:path"
|
||||
import { pathToFileURL } from "node:url"
|
||||
import type { LookAtArgs } from "./types"
|
||||
import {
|
||||
extractBase64Data,
|
||||
inferMimeTypeFromBase64,
|
||||
inferMimeTypeFromFilePath,
|
||||
} from "./mime-type-inference"
|
||||
import {
|
||||
needsConversion,
|
||||
convertImageToJpeg,
|
||||
convertBase64ImageToJpeg,
|
||||
cleanupConvertedImage,
|
||||
} from "./image-converter"
|
||||
import { log } from "../../shared"
|
||||
|
||||
export interface LookAtFilePart {
|
||||
type: "file"
|
||||
mime: string
|
||||
url: string
|
||||
filename: string
|
||||
}
|
||||
|
||||
export interface PreparedLookAtInput {
|
||||
readonly filePart: LookAtFilePart
|
||||
readonly isBase64Input: boolean
|
||||
readonly sourceDescription: string
|
||||
cleanup(): void
|
||||
}
|
||||
|
||||
type PrepareLookAtInputResult =
|
||||
| { ok: true; value: PreparedLookAtInput }
|
||||
| { ok: false; error: string }
|
||||
|
||||
function getTemporaryConversionPath(error: unknown): string | null {
|
||||
if (!(error instanceof Error)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const temporaryOutputPath = Reflect.get(error, "temporaryOutputPath")
|
||||
if (typeof temporaryOutputPath === "string" && temporaryOutputPath.length > 0) {
|
||||
return temporaryOutputPath
|
||||
}
|
||||
|
||||
const temporaryDirectory = Reflect.get(error, "temporaryDirectory")
|
||||
if (typeof temporaryDirectory === "string" && temporaryDirectory.length > 0) {
|
||||
return temporaryDirectory
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function prepareLookAtInput(args: LookAtArgs): PrepareLookAtInputResult {
|
||||
const imageData = args.image_data
|
||||
const filePath = args.file_path
|
||||
|
||||
if (imageData) {
|
||||
const mimeType = inferMimeTypeFromBase64(imageData)
|
||||
|
||||
let finalBase64Data = extractBase64Data(imageData)
|
||||
let finalMimeType = mimeType
|
||||
let tempFilesToCleanup: string[] = []
|
||||
|
||||
if (needsConversion(mimeType)) {
|
||||
log(`[look_at] Detected unsupported Base64 format: ${mimeType}, converting to JPEG...`)
|
||||
try {
|
||||
const { base64, tempFiles } = convertBase64ImageToJpeg(finalBase64Data, mimeType)
|
||||
finalBase64Data = base64
|
||||
finalMimeType = "image/jpeg"
|
||||
tempFilesToCleanup = tempFiles
|
||||
log("[look_at] Base64 conversion successful")
|
||||
} catch (conversionError) {
|
||||
log(`[look_at] Base64 conversion failed: ${conversionError}`)
|
||||
return {
|
||||
ok: false,
|
||||
error: `Error: Failed to convert Base64 image format. ${conversionError}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
value: {
|
||||
isBase64Input: true,
|
||||
sourceDescription: "clipboard/pasted image",
|
||||
filePart: {
|
||||
type: "file",
|
||||
mime: finalMimeType,
|
||||
url: `data:${finalMimeType};base64,${finalBase64Data}`,
|
||||
filename: `clipboard-image.${finalMimeType.split("/")[1] || "png"}`,
|
||||
},
|
||||
cleanup() {
|
||||
for (const temporaryFile of tempFilesToCleanup) {
|
||||
cleanupConvertedImage(temporaryFile)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (filePath) {
|
||||
let mimeType = inferMimeTypeFromFilePath(filePath)
|
||||
let actualFilePath = filePath
|
||||
let tempFilePath: string | null = null
|
||||
let tempConversionPath: string | null = null
|
||||
|
||||
if (needsConversion(mimeType)) {
|
||||
log(`[look_at] Detected unsupported format: ${mimeType}, converting to JPEG...`)
|
||||
try {
|
||||
tempFilePath = convertImageToJpeg(filePath, mimeType)
|
||||
tempConversionPath = tempFilePath
|
||||
actualFilePath = tempFilePath
|
||||
mimeType = "image/jpeg"
|
||||
log(`[look_at] Conversion successful: ${tempFilePath}`)
|
||||
} catch (conversionError) {
|
||||
const failedConversionPath = getTemporaryConversionPath(conversionError)
|
||||
if (failedConversionPath) {
|
||||
tempConversionPath = failedConversionPath
|
||||
}
|
||||
log(`[look_at] Conversion failed: ${conversionError}`)
|
||||
return {
|
||||
ok: false,
|
||||
error: `Error: Failed to convert image format. ${conversionError}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
value: {
|
||||
isBase64Input: false,
|
||||
sourceDescription: filePath,
|
||||
filePart: {
|
||||
type: "file",
|
||||
mime: mimeType,
|
||||
url: pathToFileURL(actualFilePath).href,
|
||||
filename: basename(actualFilePath),
|
||||
},
|
||||
cleanup() {
|
||||
if (tempConversionPath) {
|
||||
cleanupConvertedImage(tempConversionPath)
|
||||
} else if (tempFilePath) {
|
||||
cleanupConvertedImage(tempFilePath)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ok: false,
|
||||
error: "Error: Must provide either 'file_path' or 'image_data'.",
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user