improvement(file-block): add get operation (#4588)

* File block get

* Lint

* Fix
This commit is contained in:
Siddharth Ganesan
2026-05-13 18:34:18 -07:00
committed by GitHub
parent 4de955d0b6
commit 214355b26e
6 changed files with 168 additions and 3 deletions
@@ -8,6 +8,7 @@ import { ensureAbsoluteUrl } from '@/lib/core/utils/urls'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
import {
fetchWorkspaceFileBuffer,
getWorkspaceFile,
getWorkspaceFileByName,
updateWorkspaceFileContent,
uploadWorkspaceFile,
@@ -40,6 +41,51 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
try {
switch (body.operation) {
case 'get': {
const { fileId, fileInput } = body
const selectedFileId =
fileId ||
(fileInput && typeof fileInput === 'object' && !Array.isArray(fileInput)
? typeof fileInput.id === 'string'
? fileInput.id
: typeof fileInput.fileId === 'string'
? fileInput.fileId
: ''
: '')
if (!selectedFileId) {
return NextResponse.json({ success: false, error: 'File is required' }, { status: 400 })
}
const file = await getWorkspaceFile(workspaceId, selectedFileId)
if (!file) {
return NextResponse.json(
{ success: false, error: `File not found: "${selectedFileId}"` },
{ status: 404 }
)
}
logger.info('File retrieved', {
fileId: file.id,
name: file.name,
})
return NextResponse.json({
success: true,
data: {
file: {
id: file.id,
name: file.name,
url: ensureAbsoluteUrl(file.path),
size: file.size,
type: file.type,
key: file.key,
context: 'workspace',
},
},
})
}
case 'write': {
const { fileName, content, contentType } = body
const mimeType = contentType || getMimeTypeFromExtension(getFileExtension(fileName))
+52 -2
View File
@@ -266,6 +266,7 @@ export const FileV3Block: BlockConfig<FileParserV3Output> = {
type: 'dropdown' as SubBlockType,
options: [
{ label: 'Read', id: 'file_parser_v3' },
{ label: 'Get', id: 'file_get' },
{ label: 'Write', id: 'file_write' },
{ label: 'Append', id: 'file_append' },
],
@@ -294,6 +295,28 @@ export const FileV3Block: BlockConfig<FileParserV3Output> = {
required: { field: 'operation', value: 'file_parser_v3' },
condition: { field: 'operation', value: 'file_parser_v3' },
},
{
id: 'getFile',
title: 'File',
type: 'file-upload' as SubBlockType,
canonicalParamId: 'getFileInput',
acceptedTypes: '*',
placeholder: 'Select a workspace file',
multiple: false,
mode: 'basic',
condition: { field: 'operation', value: 'file_get' },
required: { field: 'operation', value: 'file_get' },
},
{
id: 'getFileId',
title: 'File ID',
type: 'short-input' as SubBlockType,
canonicalParamId: 'getFileInput',
placeholder: 'Workspace file ID',
mode: 'advanced',
condition: { field: 'operation', value: 'file_get' },
required: { field: 'operation', value: 'file_get' },
},
{
id: 'fileName',
title: 'File Name',
@@ -349,7 +372,7 @@ export const FileV3Block: BlockConfig<FileParserV3Output> = {
},
],
tools: {
access: ['file_parser_v3', 'file_write', 'file_append'],
access: ['file_parser_v3', 'file_get', 'file_write', 'file_append'],
config: {
tool: (params) => params.operation || 'file_parser_v3',
params: (params) => {
@@ -390,6 +413,25 @@ export const FileV3Block: BlockConfig<FileParserV3Output> = {
}
}
if (operation === 'file_get') {
const getInput = params.getFileInput
if (!getInput) {
throw new Error('File is required for get')
}
if (typeof getInput === 'string') {
return {
fileId: getInput.trim(),
workspaceId: params._context?.workspaceId,
}
}
return {
fileInput: normalizeFileInput(getInput, { single: true }),
workspaceId: params._context?.workspaceId,
}
}
const fileInput = params.fileInput
if (!fileInput) {
logger.error('No file input provided')
@@ -428,9 +470,13 @@ export const FileV3Block: BlockConfig<FileParserV3Output> = {
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform (read, write, or append)' },
operation: {
type: 'string',
description: 'Operation to perform (read, get, write, or append)',
},
fileInput: { type: 'json', description: 'File input for read' },
fileType: { type: 'string', description: 'File type for read' },
getFileInput: { type: 'json', description: 'Selected file or workspace file ID for get' },
fileName: { type: 'string', description: 'Name for a new file (write)' },
content: { type: 'string', description: 'File content to write' },
contentType: { type: 'string', description: 'MIME content type for write' },
@@ -446,6 +492,10 @@ export const FileV3Block: BlockConfig<FileParserV3Output> = {
type: 'string',
description: 'All file contents merged into a single text string (read)',
},
file: {
type: 'file',
description: 'Workspace file object (get)',
},
id: {
type: 'string',
description: 'File ID (write)',
+13 -1
View File
@@ -22,9 +22,21 @@ export const fileManageAppendBodySchema = z.object({
content: z.string({ error: 'content is required for append operation' }),
})
export const fileManageBodySchema = z.discriminatedUnion('operation', [
export const fileManageGetBodySchema = z
.object({
operation: z.literal('get'),
workspaceId: z.string().min(1).optional(),
fileId: z.string().min(1).optional(),
fileInput: z.any().optional(),
})
.refine((data) => data.fileId !== undefined || data.fileInput !== undefined, {
message: 'Either fileId or fileInput is required for get operation',
})
export const fileManageBodySchema = z.union([
fileManageWriteBodySchema,
fileManageAppendBodySchema,
fileManageGetBodySchema,
])
export const fileManageContract = defineRouteContract({
+54
View File
@@ -0,0 +1,54 @@
import type { ToolConfig, ToolResponse, WorkflowToolExecutionContext } from '@/tools/types'
interface FileGetParams {
fileId?: string
fileInput?: unknown
workspaceId?: string
_context?: WorkflowToolExecutionContext
}
export const fileGetTool: ToolConfig<FileGetParams, ToolResponse> = {
id: 'file_get',
name: 'File Get',
description: 'Get a workspace file object from a selected file or canonical workspace file ID.',
version: '1.0.0',
params: {
fileId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Canonical workspace file ID.',
},
fileInput: {
type: 'file',
required: false,
visibility: 'user-only',
description: 'Selected workspace file object.',
},
},
request: {
url: '/api/tools/file/manage',
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
operation: 'get',
fileId: params.fileId,
fileInput: params.fileInput,
workspaceId: params.workspaceId || params._context?.workspaceId,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || !data.success) {
return { success: false, output: {}, error: data.error || 'Failed to get file' }
}
return { success: true, output: data.data }
},
outputs: {
file: { type: 'file', description: 'Workspace file object' },
},
}
+1
View File
@@ -1,6 +1,7 @@
import { fileParserTool, fileParserV2Tool, fileParserV3Tool } from '@/tools/file/parser'
export { fileAppendTool } from '@/tools/file/append'
export { fileGetTool } from '@/tools/file/get'
export { fileWriteTool } from '@/tools/file/write'
export const fileParseTool = fileParserTool
+2
View File
@@ -643,6 +643,7 @@ import {
} from '@/tools/fathom'
import {
fileAppendTool,
fileGetTool,
fileParserV2Tool,
fileParserV3Tool,
fileParseTool,
@@ -3213,6 +3214,7 @@ export const tools: Record<string, ToolConfig> = {
file_parser_v2: fileParserV2Tool,
file_parser_v3: fileParserV3Tool,
file_append: fileAppendTool,
file_get: fileGetTool,
file_write: fileWriteTool,
firecrawl_scrape: firecrawlScrapeTool,
firecrawl_search: firecrawlSearchTool,