diff --git a/api/server/services/Files/Code/process.js b/api/server/services/Files/Code/process.js index 2839d08c5a..62749818cf 100644 --- a/api/server/services/Files/Code/process.js +++ b/api/server/services/Files/Code/process.js @@ -709,6 +709,41 @@ async function getSessionInfo(ref) { } } +const getPreviewContextSuffix = (file) => { + if (file.status === 'pending') { + return ' (preview not yet generated)'; + } + + if (file.status !== 'failed') { + return ''; + } + + return file.previewError + ? ` (preview unavailable: ${file.previewError})` + : ' (preview unavailable)'; +}; + +const getVisibleCodeFileContextLine = (file, agentResourceIds) => { + if (file.context === FileContext.execute_code) { + return ''; + } + + const fileSuffix = agentResourceIds.has(file.file_id) ? '' : ' (attached by user)'; + return `\n\t- /mnt/data/${file.filename}${fileSuffix}${getPreviewContextSuffix(file)}`; +}; + +const appendVisibleCodeFileContext = (toolContext, contextLine) => { + if (!contextLine) { + return toolContext; + } + + if (toolContext) { + return `${toolContext}${contextLine}`; + } + + return `- Note: The following files are available in the "${Tools.execute_code}" tool environment:${contextLine}`; +}; + /** * * @param {Object} options @@ -797,34 +832,10 @@ const primeFiles = async (options) => { * tenant prefix from auth context). */ const pushFile = (overrideSessionId, overrideId) => { - if (!toolContext) { - toolContext = `- Note: The following files are available in the "${Tools.execute_code}" tool environment:`; - } - - let fileSuffix = ''; - if (!agentResourceIds.has(file.file_id)) { - fileSuffix = - file.context === FileContext.execute_code - ? ' (from previous code execution)' - : ' (attached by user)'; - } - - /* Surface the preview lifecycle so the LLM knows when a - * prior-turn artifact's rich preview didn't materialize. The - * file blob is always available (`processCodeOutput` persists - * it before returning), so the model can still tell the user - * "you can download it" even when the preview never resolved. - * Absent status means legacy or non-office — render normally. */ - let previewSuffix = ''; - if (file.status === 'pending') { - previewSuffix = ' (preview not yet generated)'; - } else if (file.status === 'failed') { - previewSuffix = file.previewError - ? ` (preview unavailable: ${file.previewError})` - : ' (preview unavailable)'; - } - - toolContext += `\n\t- /mnt/data/${file.filename}${fileSuffix}${previewSuffix}`; + toolContext = appendVisibleCodeFileContext( + toolContext, + getVisibleCodeFileContextLine(file, agentResourceIds), + ); /* `id` is the storage file_id (drives codeapi's upload-key * existence check), `resource_id` is the entity that owns * the storage session (drives sessionKey re-derivation). For diff --git a/api/server/services/Files/Code/process.spec.js b/api/server/services/Files/Code/process.spec.js index ea2cd29429..cd2778f80d 100644 --- a/api/server/services/Files/Code/process.spec.js +++ b/api/server/services/Files/Code/process.spec.js @@ -1770,13 +1770,11 @@ describe('Code Process', () => { }); }); - describe('primeFiles toolContext surfaces preview status to the LLM', () => { - /* When a prior-turn code-execution file's HTML preview never resolved - * (still pending, or failed), the agent context for this turn must - * carry that signal so the model can tell the user "you can still - * download it, but the preview isn't available." Otherwise the model - * would refer to the file as if everything is fine and the user gets - * a confusing UI mismatch. */ + describe('primeFiles toolContext for model-visible code files', () => { + /* User-visible code-env input files keep their `/mnt/data` context and + * preview lifecycle hints. Prior-turn generated artifacts are still + * primed into the sandbox, but no longer repeated in model-visible + * instructions every turn. */ const { getStrategyFunctions } = require('~/server/services/Files/strategies'); const { getFiles } = require('~/models'); @@ -1788,7 +1786,7 @@ describe('Code Process', () => { filename: `data-${overrides.status ?? 'ready'}.xlsx`, filepath: `/uploads/${overrides.status ?? 'ready'}.xlsx`, source: 'local', - context: 'execute_code', + context: FileContext.message_attachment, metadata: { codeEnvRef: { kind: 'user', @@ -1811,6 +1809,33 @@ describe('Code Process', () => { filterFilesByAgentAccess.mockImplementation(({ files }) => Promise.resolve(files)); } + it('does not include generated code files in model-visible toolContext', async () => { + setupSessionInfoOk(); + getFiles.mockResolvedValue([ + makeFile({ + context: FileContext.execute_code, + filename: 'generated.html', + }), + ]); + + const result = await primeFiles({ + req: { user: { id: 'user-123', role: 'USER' } }, + tool_resources: { execute_code: { file_ids: ['fid-ready'], files: [] } }, + agentId: 'agent-id', + }); + + expect(result.toolContext).toBe(''); + expect(result.files).toEqual([ + { + id: 'CURRENT_ID', + resource_id: 'user-123', + storage_session_id: 'CURRENT_SESSION', + name: 'generated.html', + kind: 'user', + }, + ]); + }); + it('annotates a pending file with "(preview not yet generated)"', async () => { setupSessionInfoOk(); getFiles.mockResolvedValue([makeFile({ status: 'pending' })]);