feat(mothership): append

This commit is contained in:
Siddharth Ganesan
2026-04-04 12:31:23 -07:00
parent b6e1df4ffd
commit acc00df6a5
3 changed files with 90 additions and 1 deletions

View File

@@ -75,6 +75,16 @@ vi.mock('@/lib/uploads/utils/file-utils', () => ({
vi.mock('@/lib/uploads/setup.server', () => ({}))
vi.mock('@/lib/execution/doc-vm', () => ({
generatePdfFromCode: vi.fn().mockResolvedValue(Buffer.from('%PDF-compiled')),
generateDocxFromCode: vi.fn().mockResolvedValue(Buffer.from('PK\x03\x04compiled')),
generatePptxFromCode: vi.fn().mockResolvedValue(Buffer.from('PK\x03\x04compiled')),
}))
vi.mock('@/lib/uploads/contexts/workspace/workspace-file-manager', () => ({
parseWorkspaceFileKey: vi.fn().mockReturnValue(undefined),
}))
vi.mock('@/app/api/files/utils', () => ({
FileNotFoundError,
createFileResponse: mockCreateFileResponse,

View File

@@ -301,6 +301,85 @@ export const workspaceFileServerTool: BaseServerTool<WorkspaceFileArgs, Workspac
}
}
case 'append': {
const fileId = (args as Record<string, unknown>).fileId as string | undefined
const content = (args as Record<string, unknown>).content as string | undefined
if (!fileId) {
return { success: false, message: 'fileId is required for append operation' }
}
if (!content) {
return { success: false, message: 'content is required for append operation' }
}
const fileRecord = await getWorkspaceFile(workspaceId, fileId)
if (!fileRecord) {
return { success: false, message: `File with ID "${fileId}" not found` }
}
const currentBuffer = await downloadWsFile(fileRecord)
const combined = `${currentBuffer.toString('utf-8')}\n${content}`
const appendLowerName = fileRecord.name?.toLowerCase() ?? ''
const isPptxAppend = appendLowerName.endsWith('.pptx')
const isDocxAppend = appendLowerName.endsWith('.docx')
const isPdfAppend = appendLowerName.endsWith('.pdf')
const isDocAppend = isPptxAppend || isDocxAppend || isPdfAppend
if (isDocAppend) {
const formatName = isPptxAppend ? 'PPTX' : isDocxAppend ? 'DOCX' : 'PDF'
const generator = isPptxAppend
? generatePptxFromCode
: isDocxAppend
? generateDocxFromCode
: generatePdfFromCode
try {
await generator(combined, workspaceId)
} catch (err) {
const msg = err instanceof Error ? err.message : String(err)
return {
success: false,
message: `Appended ${formatName} code failed to compile: ${msg}. Fix the content and retry.`,
}
}
}
const appendSourceMime = isPptxAppend
? PPTX_SOURCE_MIME
: isDocxAppend
? DOCX_SOURCE_MIME
: isPdfAppend
? PDF_SOURCE_MIME
: undefined
const appendBuffer = Buffer.from(combined, 'utf-8')
assertServerToolNotAborted(context)
await updateWorkspaceFileContent(
workspaceId,
fileId,
context.userId,
appendBuffer,
appendSourceMime
)
logger.info('Workspace file appended via copilot', {
fileId,
name: fileRecord.name,
appendedSize: content.length,
totalSize: appendBuffer.length,
userId: context.userId,
})
return {
success: true,
message: `Content appended to "${fileRecord.name}" (${content.length} bytes added, ${appendBuffer.length} bytes total)`,
data: {
id: fileId,
name: fileRecord.name,
size: appendBuffer.length,
},
}
}
case 'patch': {
const fileId = (args as Record<string, unknown>).fileId as string | undefined
const edits = (args as Record<string, unknown>).edits as

View File

@@ -176,7 +176,7 @@ export type UserTableResult = z.infer<typeof UserTableResultSchema>
// workspace_file - shared schema used by server tool and Go catalog
export const WorkspaceFileArgsSchema = z.object({
operation: z.enum(['write', 'update', 'delete', 'rename', 'patch']),
operation: z.enum(['write', 'update', 'append', 'delete', 'rename', 'patch']),
args: z
.object({
fileId: z.string().optional(),