mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
fix(import): roll back stored bytes when the file row fails, and count local resources
If saveBuffer succeeded and createFile then rejected, the object was in storage with no row pointing at it - and the caller only learns an asset exists once ingestOne resolves, so nothing could ever find it to clean up. A Claude local_resource references bytes the export does not ship, same as an image, so it counts as unavailable. Rendering its label is not the same as having it.
This commit is contained in:
parent
fc57538611
commit
cd271ed7ae
3 changed files with 35 additions and 17 deletions
|
|
@ -302,21 +302,33 @@ async function ingestOne(
|
|||
|
||||
const { filepath, source } = await deps.saveBuffer({ userId, buffer, fileName, type, tenantId });
|
||||
|
||||
await deps.createFile(
|
||||
{
|
||||
user: userId,
|
||||
file_id: fileId,
|
||||
bytes: buffer.byteLength,
|
||||
filepath,
|
||||
filename: originalName,
|
||||
type,
|
||||
source,
|
||||
context: FileContext.message_attachment,
|
||||
tenantId,
|
||||
...(input.expiredAt ? { expiredAt: input.expiredAt } : {}),
|
||||
},
|
||||
true,
|
||||
);
|
||||
try {
|
||||
await deps.createFile(
|
||||
{
|
||||
user: userId,
|
||||
file_id: fileId,
|
||||
bytes: buffer.byteLength,
|
||||
filepath,
|
||||
filename: originalName,
|
||||
type,
|
||||
source,
|
||||
context: FileContext.message_attachment,
|
||||
tenantId,
|
||||
...(input.expiredAt ? { expiredAt: input.expiredAt } : {}),
|
||||
},
|
||||
true,
|
||||
);
|
||||
} catch (error) {
|
||||
/** The bytes are in storage but no row points at them, and the caller only
|
||||
* learns an asset exists once this function resolves — so nothing else
|
||||
* could ever find this object to clean up. */
|
||||
try {
|
||||
await deps.deleteFile?.({ file_id: fileId, filepath, filename: originalName, type });
|
||||
} catch (cleanupError) {
|
||||
logger.error(`[import] Could not roll back the stored asset ${fileId}`, cleanupError);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return buildImportedAsset(
|
||||
fileId,
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ describe('completeToolCall', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('counts image results as unavailable and names local resources', () => {
|
||||
it('counts images and local resources as unavailable, and still names the resource', () => {
|
||||
const registry = createToolRegistry();
|
||||
const sources = createSourceIndex();
|
||||
beginToolCall(registry, use('tu-1', 'present_files'));
|
||||
|
|
@ -132,7 +132,9 @@ describe('completeToolCall', () => {
|
|||
sources,
|
||||
);
|
||||
|
||||
expect(resolved.unavailable).toBe(2);
|
||||
/** All three reference bytes the export does not ship. `local_resource`
|
||||
* renders a label, which is not the same as being available. */
|
||||
expect(resolved.unavailable).toBe(3);
|
||||
expect(resolved.output).toBe('- notes');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -127,6 +127,10 @@ function renderResult(blocks: ClaudeResultBlock[], sources: SourceIndex): Result
|
|||
}
|
||||
|
||||
if (block.type === 'local_resource') {
|
||||
/** The export ships the name or path but never the bytes, so this is a
|
||||
* missing attachment even though it renders a label. Not counting it made
|
||||
* the report understate what the import could not bring across. */
|
||||
unavailable += 1;
|
||||
const label = block.name ?? block.file_path;
|
||||
if (label) {
|
||||
lines.push(`- ${label}`);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue