From ebcc8dd114507944bdb51658a64416e1cdde40bf Mon Sep 17 00:00:00 2001 From: Allen Hutchison Date: Fri, 21 Nov 2025 14:41:21 -0800 Subject: [PATCH 1/2] feat: add orderBy support to chat.getMessages --- gemini-extension.json | 8 +++--- .../__tests__/services/ChatService.test.ts | 27 +++++++++++++++++++ workspace-server/src/index.ts | 1 + workspace-server/src/services/ChatService.ts | 6 +++-- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/gemini-extension.json b/gemini-extension.json index c154e3c..c6e94c3 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,13 +1,15 @@ { "name": "google-workspace", - "version": "DEV", + "version": "0.0.2", "contextFileName": "workspace-server${/}WORKSPACE-Context.md", "mcpServers": { "google-workspace": { "program": "./workspace-server/dist/index.js", "command": "node", - "args": ["scripts${/}start.js"], + "args": [ + "scripts${/}start.js" + ], "cwd": "${extensionPath}" } } -} +} \ No newline at end of file diff --git a/workspace-server/src/__tests__/services/ChatService.test.ts b/workspace-server/src/__tests__/services/ChatService.test.ts index d75287d..1ffbe17 100644 --- a/workspace-server/src/__tests__/services/ChatService.test.ts +++ b/workspace-server/src/__tests__/services/ChatService.test.ts @@ -354,6 +354,33 @@ describe('ChatService', () => { const response = JSON.parse(result.content[0].text); expect(response.messages).toEqual(mockMessages); }); + + it('should pass orderBy to the API', async () => { + const mockMessages = [ + { name: 'spaces/space1/messages/msg1', text: 'Hello' }, + ]; + + mockChatAPI.spaces.messages.list.mockResolvedValue({ + data: { + messages: mockMessages, + }, + }); + + const result = await chatService.getMessages({ + spaceName: 'spaces/space1', + orderBy: 'createTime desc', + }); + + expect(mockChatAPI.spaces.messages.list).toHaveBeenCalledWith({ + parent: 'spaces/space1', + pageSize: undefined, + pageToken: undefined, + orderBy: 'createTime desc', + }); + + const response = JSON.parse(result.content[0].text); + expect(response.messages).toEqual(mockMessages); + }); }); describe('sendDm', () => { diff --git a/workspace-server/src/index.ts b/workspace-server/src/index.ts index 83af4a8..31a0260 100644 --- a/workspace-server/src/index.ts +++ b/workspace-server/src/index.ts @@ -470,6 +470,7 @@ async function main() { unreadOnly: z.boolean().optional().describe('Whether to return only unread messages.'), pageSize: z.number().optional().describe('The maximum number of messages to return.'), pageToken: z.string().optional().describe('The token for the next page of results.'), + orderBy: z.string().optional().describe('The order to list messages in (e.g., "createTime desc").'), } }, chatService.getMessages diff --git a/workspace-server/src/services/ChatService.ts b/workspace-server/src/services/ChatService.ts index 2142efe..935f802 100644 --- a/workspace-server/src/services/ChatService.ts +++ b/workspace-server/src/services/ChatService.ts @@ -176,7 +176,7 @@ export class ChatService { } } - public getMessages = async ({ spaceName, unreadOnly, pageSize, pageToken }: { spaceName: string, unreadOnly?: boolean, pageSize?: number, pageToken?: string }) => { + public getMessages = async ({ spaceName, unreadOnly, pageSize, pageToken, orderBy }: { spaceName: string, unreadOnly?: boolean, pageSize?: number, pageToken?: string, orderBy?: string }) => { logToFile(`Listing messages for space: ${spaceName}`); try { const chat = await this.getChatClient(); @@ -207,7 +207,7 @@ export class ChatService { logToFile(`No last read time found for user in space: ${spaceName}`); // This can happen if the user has never read messages in the space. // In this case, all messages are unread. - const res = await chat.spaces.messages.list({ parent: spaceName, pageSize, pageToken }); + const res = await chat.spaces.messages.list({ parent: spaceName, pageSize, pageToken, orderBy }); const messages = res.data.messages || []; logToFile(`Successfully listed ${messages.length} unread messages for space: ${spaceName}`); return { content: [{ type: "text" as const, text: JSON.stringify({ messages, nextPageToken: res.data.nextPageToken }) }] }; @@ -218,6 +218,7 @@ export class ChatService { filter: `createTime > "${lastReadTime}"`, pageSize, pageToken, + orderBy, }); const messages = res.data.messages || []; @@ -234,6 +235,7 @@ export class ChatService { parent: spaceName, pageSize, pageToken, + orderBy, }); const messages = res.data.messages || []; logToFile(`Successfully listed ${messages.length} messages for space: ${spaceName}`); From 2f7b6b93299e5bcd9ce5fe43181b4cb73cd53d35 Mon Sep 17 00:00:00 2001 From: Allen Hutchison Date: Fri, 21 Nov 2025 14:58:08 -0800 Subject: [PATCH 2/2] refactor: consolidate message listing logic in ChatService --- workspace-server/src/services/ChatService.ts | 63 ++++++++------------ 1 file changed, 25 insertions(+), 38 deletions(-) diff --git a/workspace-server/src/services/ChatService.ts b/workspace-server/src/services/ChatService.ts index 935f802..530449e 100644 --- a/workspace-server/src/services/ChatService.ts +++ b/workspace-server/src/services/ChatService.ts @@ -180,6 +180,8 @@ export class ChatService { logToFile(`Listing messages for space: ${spaceName}`); try { const chat = await this.getChatClient(); + let filter: string | undefined; + if (unreadOnly) { const people = await this.getPeopleClient(); const person = await people.people.get({ @@ -203,49 +205,33 @@ export class ChatService { const lastReadTime = currentUserMember?.lastReadTime; - if (!lastReadTime) { + if (lastReadTime) { + filter = `createTime > "${lastReadTime}"`; + } else { logToFile(`No last read time found for user in space: ${spaceName}`); - // This can happen if the user has never read messages in the space. - // In this case, all messages are unread. - const res = await chat.spaces.messages.list({ parent: spaceName, pageSize, pageToken, orderBy }); - const messages = res.data.messages || []; - logToFile(`Successfully listed ${messages.length} unread messages for space: ${spaceName}`); - return { content: [{ type: "text" as const, text: JSON.stringify({ messages, nextPageToken: res.data.nextPageToken }) }] }; } + } - const res = await chat.spaces.messages.list({ - parent: spaceName, - filter: `createTime > "${lastReadTime}"`, - pageSize, - pageToken, - orderBy, - }); + const res = await chat.spaces.messages.list({ + parent: spaceName, + filter, + pageSize, + pageToken, + orderBy, + }); - const messages = res.data.messages || []; - logToFile(`Successfully listed ${messages.length} unread messages for space: ${spaceName}`); - return { - content: [{ - type: "text" as const, - text: JSON.stringify({ messages, nextPageToken: res.data.nextPageToken }) - }] - }; + const messages = res.data.messages || []; + const logMessage = unreadOnly + ? `Successfully listed ${messages.length} unread messages for space: ${spaceName}` + : `Successfully listed ${messages.length} messages for space: ${spaceName}`; + logToFile(logMessage); - } else { - const res = await chat.spaces.messages.list({ - parent: spaceName, - pageSize, - pageToken, - orderBy, - }); - const messages = res.data.messages || []; - logToFile(`Successfully listed ${messages.length} messages for space: ${spaceName}`); - return { - content: [{ - type: "text" as const, - text: JSON.stringify({ messages, nextPageToken: res.data.nextPageToken }) - }] - }; - } + return { + content: [{ + type: "text" as const, + text: JSON.stringify({ messages, nextPageToken: res.data.nextPageToken }) + }] + }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logToFile(`Error during chat.getMessages: ${errorMessage}`); @@ -265,6 +251,7 @@ export class ChatService { } } + public sendDm = async ({ email, message }: { email: string, message: string }) => { logToFile(`chat.sendDm called with: email=${email}, message=${message}`); try {