From 7e937df05ad3bd4f958e53c21b42084f80a151d2 Mon Sep 17 00:00:00 2001 From: Aron Gates Date: Fri, 8 May 2026 10:38:22 -0400 Subject: [PATCH] fix: forward isTemporary from client for retention on file uploads and tool calls Server-side `getRetentionExpiry` (file uploads) and the tool-call controller both read `req.body.isTemporary`, but the file upload multipart form and the tool-call payload did not include that field. In `retentionMode: temporary` (default), files uploaded and tool calls created from temporary chats were therefore retained indefinitely. Forward the Recoil `isTemporary` flag in both client paths so the existing server checks can fire correctly. `ToolParams` gains an optional `isTemporary` field. Addresses Codex P1 review feedback on PR #29. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/components/Messages/Content/RunCode.tsx | 15 ++++++++++++++- client/src/hooks/Files/useFileHandling.ts | 8 ++++++-- packages/data-provider/src/types/mutations.ts | 1 + 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/client/src/components/Messages/Content/RunCode.tsx b/client/src/components/Messages/Content/RunCode.tsx index da7db2739a..6bb0febf74 100644 --- a/client/src/components/Messages/Content/RunCode.tsx +++ b/client/src/components/Messages/Content/RunCode.tsx @@ -1,5 +1,6 @@ import React, { useState, useMemo, useCallback, useEffect, useRef } from 'react'; import debounce from 'lodash/debounce'; +import { useRecoilValue } from 'recoil'; import { Tools } from 'librechat-data-provider'; import { TerminalSquareIcon, Check, X } from 'lucide-react'; import { Spinner, TooltipAnchor, useToastContext } from '@librechat/client'; @@ -8,6 +9,7 @@ import { useToolCallMutation } from '~/data-provider'; import { useLocalize } from '~/hooks'; import { cn, normalizeLanguage } from '~/utils'; import { useMessageContext } from '~/Providers'; +import store from '~/store'; type RunState = 'idle' | 'loading' | 'success' | 'error'; @@ -23,6 +25,7 @@ const RunCode: React.FC = React.memo( const { messageId, conversationId, partIndex } = useMessageContext(); const normalizedLang = useMemo(() => normalizeLanguage(lang), [lang]); + const isTemporary = useRecoilValue(store.isTemporary); const handleExecute = useCallback(async () => { const codeString: string = codeRef.current?.textContent ?? ''; @@ -42,8 +45,18 @@ const RunCode: React.FC = React.memo( conversationId: conversationId ?? '', lang: normalizedLang, code: codeString, + isTemporary, }); - }, [codeRef, execute, partIndex, messageId, blockIndex, conversationId, normalizedLang]); + }, [ + codeRef, + execute, + partIndex, + messageId, + blockIndex, + conversationId, + normalizedLang, + isTemporary, + ]); const debouncedExecute = useMemo( () => debounce(handleExecute, 1000, { leading: true }), diff --git a/client/src/hooks/Files/useFileHandling.ts b/client/src/hooks/Files/useFileHandling.ts index 635937a6fa..7092242bd9 100644 --- a/client/src/hooks/Files/useFileHandling.ts +++ b/client/src/hooks/Files/useFileHandling.ts @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useRef, useMemo, useState } from 'react'; import { v4 } from 'uuid'; -import { useSetRecoilState } from 'recoil'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useToastContext } from '@librechat/client'; import { useQueryClient } from '@tanstack/react-query'; import { @@ -22,7 +22,7 @@ import useLocalize, { TranslationKeys } from '~/hooks/useLocalize'; import { useDelayedUploadToast } from './useDelayedUploadToast'; import { processFileForUpload } from '~/utils/heicConverter'; import { useChatContext } from '~/Providers/ChatContext'; -import { ephemeralAgentByConvoId } from '~/store'; +import store, { ephemeralAgentByConvoId } from '~/store'; import useClientResize from './useClientResize'; import useUpdateFiles from './useUpdateFiles'; @@ -57,6 +57,7 @@ const useFileHandlingCore = (params: UseFileHandling | undefined, fileState: Fil const setEphemeralAgent = useSetRecoilState( ephemeralAgentByConvoId(conversation?.conversationId ?? Constants.NEW_CONVO), ); + const isTemporary = useRecoilValue(store.isTemporary); const setError = (error: string) => setErrors((prevErrors) => [...prevErrors, error]); const { addFile, replaceFile, updateFileById, deleteFileById } = useUpdateFiles( params?.fileSetter ?? setFiles, @@ -192,6 +193,9 @@ const useFileHandlingCore = (params: UseFileHandling | undefined, fileState: Fil formData.append('endpointType', endpointType ?? ''); formData.append('file', extendedFile.file as File, encodeURIComponent(filename)); formData.append('file_id', extendedFile.file_id); + if (isTemporary) { + formData.append('isTemporary', 'true'); + } const width = extendedFile.width ?? 0; const height = extendedFile.height ?? 0; diff --git a/packages/data-provider/src/types/mutations.ts b/packages/data-provider/src/types/mutations.ts index 4f40794527..d0f69cac18 100644 --- a/packages/data-provider/src/types/mutations.ts +++ b/packages/data-provider/src/types/mutations.ts @@ -446,6 +446,7 @@ export type ToolParams = ToolParamsMap[T] & { partIndex?: number; blockIndex?: number; conversationId: string; + isTemporary?: boolean; }; export type ToolCallResponse = { result: unknown; attachments?: types.TAttachment[] }; export type ToolCallMutationOptions = MutationOptions<