Skip to content

Commit aeb77dd

Browse files
fix(files): allow document compiler to read referenced images (#6647)
1 parent 1faac4e commit aeb77dd

2 files changed

Lines changed: 128 additions & 1 deletion

File tree

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { beforeEach, describe, expect, it, vi } from 'vitest'
5+
6+
const {
7+
buildEmbeddedImageRefWarningMock,
8+
compileDocForWriteMock,
9+
consumeLatestFileIntentMock,
10+
executeCopilotFileUseCaseMock,
11+
getDocumentFormatInfoMock,
12+
inferContentTypeMock,
13+
resolveCopilotFilePrincipalMock,
14+
} = vi.hoisted(() => ({
15+
buildEmbeddedImageRefWarningMock: vi.fn(),
16+
compileDocForWriteMock: vi.fn(),
17+
consumeLatestFileIntentMock: vi.fn(),
18+
executeCopilotFileUseCaseMock: vi.fn(),
19+
getDocumentFormatInfoMock: vi.fn(),
20+
inferContentTypeMock: vi.fn(),
21+
resolveCopilotFilePrincipalMock: vi.fn(),
22+
}))
23+
24+
vi.mock('@/lib/copilot/application/execute-file-use-case', () => ({
25+
executeCopilotFileUseCase: executeCopilotFileUseCaseMock,
26+
}))
27+
vi.mock('@/lib/copilot/auth/file-delegation', () => ({
28+
messageForCopilotFileError: vi.fn((_error: unknown, fallback: string) => fallback),
29+
resolveCopilotFilePrincipal: resolveCopilotFilePrincipalMock,
30+
}))
31+
vi.mock('@/lib/core/config/env-flags', () => ({
32+
isDocSandboxEnabled: false,
33+
}))
34+
vi.mock('@/lib/workspace-files/application/update-workspace-file-content', () => ({
35+
updateWorkspaceFileContent: { operation: { id: 'files.update_content' } },
36+
}))
37+
vi.mock('@/lib/copilot/tools/server/files/doc-compile', () => ({
38+
getE2BDocFormat: vi.fn(),
39+
}))
40+
vi.mock('@/lib/copilot/tools/server/files/embedded-image-refs', () => ({
41+
buildEmbeddedImageRefWarning: buildEmbeddedImageRefWarningMock,
42+
}))
43+
vi.mock('@/lib/copilot/tools/server/files/file-intent-store', () => ({
44+
consumeLatestFileIntent: consumeLatestFileIntentMock,
45+
}))
46+
vi.mock('@/lib/copilot/tools/server/files/workspace-file', () => ({
47+
compileDocForWrite: compileDocForWriteMock,
48+
getDocumentFormatInfo: getDocumentFormatInfoMock,
49+
inferContentType: inferContentTypeMock,
50+
}))
51+
52+
import { editContentServerTool } from '@/lib/copilot/tools/server/files/edit-content'
53+
import { updateWorkspaceFileContent } from '@/lib/workspace-files/application/update-workspace-file-content'
54+
55+
const context = {
56+
userId: 'user-1',
57+
workspaceId: 'workspace-1',
58+
chatId: 'chat-1',
59+
messageId: 'message-1',
60+
toolCallId: 'tool-call-1',
61+
copilotToolExecution: true,
62+
} as const
63+
64+
const workspacePrincipal = {
65+
kind: 'delegated',
66+
serviceId: 'copilot',
67+
subjectUserId: 'user-1',
68+
workspaceId: 'workspace-1',
69+
delegationId: 'copilot-tool:tool-call-1',
70+
audience: 'sim:workspace-files',
71+
issuedAt: new Date('2026-08-12T00:00:00.000Z'),
72+
expiresAt: new Date('2026-08-12T00:05:00.000Z'),
73+
resourceScope: { chatId: 'chat-1' },
74+
} as const
75+
76+
describe('edit_content', () => {
77+
beforeEach(() => {
78+
vi.clearAllMocks()
79+
resolveCopilotFilePrincipalMock.mockReturnValue(workspacePrincipal)
80+
getDocumentFormatInfoMock.mockReturnValue({ isDoc: true })
81+
inferContentTypeMock.mockReturnValue('text/x-python-pdf')
82+
compileDocForWriteMock.mockResolvedValue({ ok: true, sourceMime: 'text/x-python-pdf' })
83+
executeCopilotFileUseCaseMock.mockResolvedValue({})
84+
buildEmbeddedImageRefWarningMock.mockResolvedValue('')
85+
consumeLatestFileIntentMock.mockResolvedValue({
86+
operation: 'update',
87+
fileId: 'pdf-1',
88+
workspaceId: 'workspace-1',
89+
userId: 'user-1',
90+
chatId: 'chat-1',
91+
messageId: 'message-1',
92+
fileRecord: {
93+
id: 'pdf-1',
94+
name: 'report.pdf',
95+
},
96+
contentType: 'text/x-python-pdf',
97+
createdAt: Date.now(),
98+
})
99+
})
100+
101+
it('compiles with workspace scope while keeping the destination write file-scoped', async () => {
102+
const content = "image = ImageReader('/home/user/inputs/image-1')"
103+
104+
await expect(editContentServerTool.execute({ content }, context)).resolves.toMatchObject({
105+
success: true,
106+
})
107+
108+
expect(resolveCopilotFilePrincipalMock).toHaveBeenCalledWith(context)
109+
expect(compileDocForWriteMock).toHaveBeenCalledWith(
110+
expect.objectContaining({
111+
source: content,
112+
fileName: 'report.pdf',
113+
workspaceId: 'workspace-1',
114+
principal: workspacePrincipal,
115+
})
116+
)
117+
expect(executeCopilotFileUseCaseMock).toHaveBeenCalledWith(
118+
context,
119+
updateWorkspaceFileContent,
120+
expect.objectContaining({
121+
fileId: 'pdf-1',
122+
assertedWorkspaceId: 'workspace-1',
123+
}),
124+
{ fileId: 'pdf-1' }
125+
)
126+
})
127+
})

apps/sim/lib/copilot/tools/server/files/edit-content.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ export const editContentServerTool: BaseServerTool<EditContentArgs, EditContentR
224224

225225
// Compile once via the right engine (or isolated-vm fallback) and resolve
226226
// the source MIME to store. Shared with the create path.
227-
const principal = resolveCopilotFilePrincipal(context, intent.fileId)
227+
const principal = resolveCopilotFilePrincipal(context)
228228
const compiled = await compileDocForWrite({
229229
source: finalContent,
230230
fileName: fileRecord.name,

0 commit comments

Comments
 (0)