diff --git a/workspace-server/src/__tests__/services/DocsService.test.ts b/workspace-server/src/__tests__/services/DocsService.test.ts index b33553ac..b7c9d7ae 100644 --- a/workspace-server/src/__tests__/services/DocsService.test.ts +++ b/workspace-server/src/__tests__/services/DocsService.test.ts @@ -117,7 +117,12 @@ describe('DocsService', () => { markdown: '# Hello', }); - expect(mockDriveAPI.files.create).toHaveBeenCalled(); + expect(mockDriveAPI.files.create).toHaveBeenCalledWith( + expect.objectContaining({ + supportsAllDrives: true, + }), + expect.any(Object), + ); expect(JSON.parse(result.content[0].text)).toEqual({ documentId: 'test-doc-id', title: 'Test Title', @@ -157,6 +162,7 @@ describe('DocsService', () => { addParents: 'test-folder-id', removeParents: 'root', fields: 'id, parents', + supportsAllDrives: true, }); }); @@ -269,6 +275,8 @@ describe('DocsService', () => { expect(mockDriveAPI.files.list).toHaveBeenCalledWith( expect.objectContaining({ q: expect.stringContaining("fullText contains 'Test'"), + supportsAllDrives: true, + includeItemsFromAllDrives: true, }), ); expect(JSON.parse(result.content[0].text)).toEqual({ @@ -336,11 +344,17 @@ describe('DocsService', () => { expect(mockDriveService.findFolder).toHaveBeenCalledWith({ folderName: 'Test Folder', }); + expect(mockDriveAPI.files.get).toHaveBeenCalledWith( + expect.objectContaining({ + supportsAllDrives: true, + }), + ); expect(mockDriveAPI.files.update).toHaveBeenCalledWith({ fileId: 'test-doc-id', addParents: 'test-folder-id', removeParents: 'root', fields: 'id, parents', + supportsAllDrives: true, }); expect(result.content[0].text).toBe( 'Moved document test-doc-id to folder Test Folder', diff --git a/workspace-server/src/__tests__/services/DriveService.test.ts b/workspace-server/src/__tests__/services/DriveService.test.ts index bdc4c233..2bb8150a 100644 --- a/workspace-server/src/__tests__/services/DriveService.test.ts +++ b/workspace-server/src/__tests__/services/DriveService.test.ts @@ -884,6 +884,51 @@ describe('DriveService', () => { ); }); + it('should download files when provided with a full Drive URL', async () => { + const mockFileId = 'file-id-from-url'; + const mockUrl = `https://drive.google.com/file/d/${mockFileId}/view`; + const mockContent = 'Hello, World!'; + const mockBuffer = Buffer.from(mockContent); + const mockLocalPath = 'downloads/test.txt'; + + mockDriveAPI.files.get.mockImplementation((params: any) => { + if (params.alt === 'media') { + return Promise.resolve({ + data: mockBuffer, + }); + } + return Promise.resolve({ + data: { id: mockFileId, name: 'test.txt', mimeType: 'text/plain' }, + }); + }); + + const result = await driveService.downloadFile({ + fileId: mockUrl, + localPath: mockLocalPath, + }); + + expect(mockDriveAPI.files.get).toHaveBeenCalledWith( + expect.objectContaining({ + fileId: mockFileId, + fields: 'id, name, mimeType', + supportsAllDrives: true, + }), + ); + + expect(mockDriveAPI.files.get).toHaveBeenCalledWith( + expect.objectContaining({ + fileId: mockFileId, + alt: 'media', + supportsAllDrives: true, + }), + expect.any(Object), + ); + + expect(result.content[0].text).toContain( + `Successfully downloaded file test.txt`, + ); + }); + it('should suggest specialized tools for workspace types', async () => { const mockFileId = 'doc-id'; mockDriveAPI.files.get.mockResolvedValue({ diff --git a/workspace-server/src/__tests__/services/SheetsService.test.ts b/workspace-server/src/__tests__/services/SheetsService.test.ts index e61b675c..ac5fc7fd 100644 --- a/workspace-server/src/__tests__/services/SheetsService.test.ts +++ b/workspace-server/src/__tests__/services/SheetsService.test.ts @@ -328,6 +328,8 @@ describe('SheetsService', () => { fields: 'nextPageToken, files(id, name)', q: "mimeType='application/vnd.google-apps.spreadsheet' and fullText contains 'budget'", pageToken: undefined, + supportsAllDrives: true, + includeItemsFromAllDrives: true, }); expect(response.files).toHaveLength(2); diff --git a/workspace-server/src/__tests__/services/SlidesService.test.ts b/workspace-server/src/__tests__/services/SlidesService.test.ts index c1351c28..b1f180ee 100644 --- a/workspace-server/src/__tests__/services/SlidesService.test.ts +++ b/workspace-server/src/__tests__/services/SlidesService.test.ts @@ -217,6 +217,8 @@ describe('SlidesService', () => { fields: 'nextPageToken, files(id, name)', q: "mimeType='application/vnd.google-apps.presentation' and fullText contains 'test query'", pageToken: undefined, + supportsAllDrives: true, + includeItemsFromAllDrives: true, }); expect(response.files).toHaveLength(2); diff --git a/workspace-server/src/services/DocsService.ts b/workspace-server/src/services/DocsService.ts index b62b7df3..b90f66af 100644 --- a/workspace-server/src/services/DocsService.ts +++ b/workspace-server/src/services/DocsService.ts @@ -83,6 +83,7 @@ export class DocsService { requestBody: fileMetadata, media: media, fields: 'id, name', + supportsAllDrives: true, }, mediaUploadOptions, ); @@ -235,6 +236,8 @@ export class DocsService { fields: 'nextPageToken, files(id, name)', q: q, pageToken: pageToken, + supportsAllDrives: true, + includeItemsFromAllDrives: true, }); const files = res.data.files || []; @@ -798,6 +801,7 @@ export class DocsService { const file = await drive.files.get({ fileId: documentId, fields: 'parents', + supportsAllDrives: true, }); const previousParents = file.data.parents?.join(','); @@ -807,6 +811,7 @@ export class DocsService { addParents: folderId, removeParents: previousParents, fields: 'id, parents', + supportsAllDrives: true, }); } catch (error) { if (error instanceof Error) { diff --git a/workspace-server/src/services/DriveService.ts b/workspace-server/src/services/DriveService.ts index b01ad68b..040c8314 100644 --- a/workspace-server/src/services/DriveService.ts +++ b/workspace-server/src/services/DriveService.ts @@ -9,6 +9,7 @@ import { AuthManager } from '../auth/AuthManager'; import { logToFile } from '../utils/logger'; import { gaxiosOptions } from '../utils/GaxiosConfig'; import { escapeQueryString } from '../utils/DriveQueryBuilder'; +import { extractDocumentId } from '../utils/validation'; import * as fs from 'node:fs'; import * as path from 'node:path'; import { PROJECT_ROOT } from '../utils/paths'; @@ -355,10 +356,11 @@ export class DriveService { logToFile(`Downloading Drive file ${fileId} to ${localPath}`); try { const drive = await this.getDriveClient(); + const id = extractDocumentId(fileId); // 1. Check if it's a Google Doc (special handling required, export instead of download) const metadata = await drive.files.get({ - fileId: fileId, + fileId: id, fields: 'id, name, mimeType', supportsAllDrives: true, }); @@ -391,7 +393,7 @@ export class DriveService { content: [ { type: 'text' as const, - text: `This is a ${fileInfo.type}. Direct download is not supported. Please use the '${fileInfo.tool}' tool with ${fileInfo.idName}: ${fileId}`, + text: `This is a ${fileInfo.type}. Direct download is not supported. Please use the '${fileInfo.tool}' tool with ${fileInfo.idName}: ${id}`, }, ], }; @@ -411,7 +413,7 @@ export class DriveService { // 2. Download media const response = await drive.files.get( { - fileId: fileId, + fileId: id, alt: 'media', supportsAllDrives: true, }, diff --git a/workspace-server/src/services/SheetsService.ts b/workspace-server/src/services/SheetsService.ts index e981aeb3..fb8df36f 100644 --- a/workspace-server/src/services/SheetsService.ts +++ b/workspace-server/src/services/SheetsService.ts @@ -223,6 +223,8 @@ export class SheetsService { fields: 'nextPageToken, files(id, name)', q: q, pageToken: pageToken, + supportsAllDrives: true, + includeItemsFromAllDrives: true, }); const files = res.data.files || []; diff --git a/workspace-server/src/services/SlidesService.ts b/workspace-server/src/services/SlidesService.ts index 46b18da1..d9627d7f 100644 --- a/workspace-server/src/services/SlidesService.ts +++ b/workspace-server/src/services/SlidesService.ts @@ -154,6 +154,8 @@ export class SlidesService { fields: 'nextPageToken, files(id, name)', q: q, pageToken: pageToken, + supportsAllDrives: true, + includeItemsFromAllDrives: true, }); const files = res.data.files || [];