mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
refactor: improve file upload error handling and return errors array
Replace errorMessage string with uploadErrors array in processAttachments to provide structured error handling. Enhanced error messages to include file names. Added getHeader helper method for case-insensitive header lookups. Fixed Gemini upload to use contentType parameter instead of Content-Type header.
This commit is contained in:
parent
7c1d222b44
commit
3e19a08aa8
6 changed files with 23 additions and 23 deletions
|
|
@ -133,33 +133,27 @@ export abstract class BaseAIClass implements IAIClass {
|
|||
protected async processAttachments<T>(
|
||||
attachments: Attachment[],
|
||||
formatBinaryFiles: (attachments: Attachment[]) => string
|
||||
): Promise<{ formattedParts: T[], errorMessage?: string }> {
|
||||
const failedUploads: string[] = [];
|
||||
): Promise<{ formattedParts: T[], uploadErrors: Error[] }> {
|
||||
const uploadErrors: Error[] = [];
|
||||
|
||||
for (const attachment of attachments) {
|
||||
try {
|
||||
if (attachment.base64.trim() === "") {
|
||||
Exception.throw("File has no content!");
|
||||
Exception.throw(`Failed to upload ${attachment.fileName}: File has no content`);
|
||||
}
|
||||
await this.aiFileService.uploadFile(attachment);
|
||||
if (!attachment.getFileID(this.provider)) {
|
||||
Exception.throw("File ID undefined after upload attempt");
|
||||
Exception.throw(`Failed to upload ${attachment.fileName}: File ID undefined after upload attempt`);
|
||||
}
|
||||
} catch (error) {
|
||||
Exception.log(`Failed to upload ${attachment.fileName}: ${Exception.messageFrom(error)}`);
|
||||
failedUploads.push(attachment.fileName);
|
||||
uploadErrors.push(Exception.new(error));
|
||||
}
|
||||
}
|
||||
|
||||
const formattedContent = formatBinaryFiles(attachments);
|
||||
const formattedParts = JSON.parse(formattedContent) as T[];
|
||||
|
||||
return {
|
||||
formattedParts,
|
||||
errorMessage: failedUploads.length > 0
|
||||
? `[Upload failed for: ${failedUploads.join(', ')}.]`
|
||||
: undefined
|
||||
};
|
||||
return { formattedParts, uploadErrors };
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -160,4 +160,9 @@ export abstract class BaseAIFileService implements IAIFileService {
|
|||
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
||||
}
|
||||
|
||||
protected getHeader(headerName: string, headers: Record<string, string>): string | undefined {
|
||||
const header = Object.keys(headers).find(header => header.toLowerCase() === headerName.toLowerCase());
|
||||
return header ? headers[header] : undefined;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,17 +205,17 @@ export class Claude extends BaseAIClass {
|
|||
|
||||
// Add binary file attachments if present
|
||||
if (content.attachments && content.attachments.length > 0) {
|
||||
const { formattedParts, errorMessage } = await this.processAttachments<ContentBlockParam>(
|
||||
const { formattedParts, uploadErrors } = await this.processAttachments<ContentBlockParam>(
|
||||
content.attachments,
|
||||
(attachments) => this.formatBinaryFiles(attachments)
|
||||
);
|
||||
|
||||
contentBlocks.push(...formattedParts);
|
||||
|
||||
if (errorMessage) {
|
||||
for (const uploadError of uploadErrors) {
|
||||
contentBlocks.push({
|
||||
type: "text",
|
||||
text: errorMessage
|
||||
text: Exception.messageFrom(uploadError)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { FinishReason } from "@google/genai";
|
|||
import { MimeType, toMimeType } from "Enums/MimeType";
|
||||
import { isTextFile } from "Enums/FileType";
|
||||
import { MimeTypeToFileTypes } from "Enums/FileTypeMimeTypeMapping";
|
||||
import { Exception } from "Helpers/Exception";
|
||||
|
||||
export class Gemini extends BaseAIClass {
|
||||
|
||||
|
|
@ -250,16 +251,16 @@ export class Gemini extends BaseAIClass {
|
|||
|
||||
// Add binary file attachments if present
|
||||
if (content.attachments && content.attachments.length > 0) {
|
||||
const { formattedParts, errorMessage } = await this.processAttachments<Part>(
|
||||
const { formattedParts, uploadErrors } = await this.processAttachments<Part>(
|
||||
content.attachments,
|
||||
(attachments) => this.formatBinaryFiles(attachments)
|
||||
);
|
||||
|
||||
parts.push(...formattedParts);
|
||||
|
||||
if (errorMessage) {
|
||||
for (const uploadError of uploadErrors) {
|
||||
parts.push({
|
||||
text: errorMessage
|
||||
text: Exception.messageFrom(uploadError)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ export class GeminiFileService extends BaseAIFileService {
|
|||
throw ApiError.fromResponse(initiateResponse.status, "Upload file failed", initiateResponse.text);
|
||||
}
|
||||
|
||||
const uploadUrl = initiateResponse.headers["x-goog-upload-url"];
|
||||
const uploadUrl = this.getHeader("x-goog-upload-url", initiateResponse.headers);
|
||||
if (!uploadUrl) {
|
||||
Exception.throw("No upload URL received from initiate request");
|
||||
}
|
||||
|
|
@ -73,8 +73,8 @@ export class GeminiFileService extends BaseAIFileService {
|
|||
const uploadResponse = await requestUrl({
|
||||
url: uploadUrl,
|
||||
method: "POST",
|
||||
contentType: mimeType,
|
||||
headers: {
|
||||
"Content-Type": mimeType,
|
||||
"X-Goog-Upload-Offset": "0",
|
||||
"X-Goog-Upload-Command": "upload, finalize"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -246,18 +246,18 @@ export class OpenAI extends BaseAIClass {
|
|||
|
||||
// Case 2: Binary file attachments
|
||||
if (content.attachments && content.attachments.length > 0) {
|
||||
const { formattedParts, errorMessage } = await this.processAttachments<ResponsesAPIInput>(
|
||||
const { formattedParts, uploadErrors } = await this.processAttachments<ResponsesAPIInput>(
|
||||
content.attachments,
|
||||
(attachments) => this.formatBinaryFiles(attachments)
|
||||
);
|
||||
|
||||
results.push(...formattedParts);
|
||||
|
||||
if (errorMessage) {
|
||||
for (const uploadError of uploadErrors) {
|
||||
// OpenAI formatBinaryFiles returns array with role wrapper, so add as separate message
|
||||
results.push({
|
||||
role: "user",
|
||||
content: errorMessage
|
||||
content: Exception.messageFrom(uploadError)
|
||||
});
|
||||
}
|
||||
continue;
|
||||
|
|
|
|||
Loading…
Reference in a new issue