From 088ea78a48f39bd41708ebe1ada311a1b4ab4e06 Mon Sep 17 00:00:00 2001 From: oskarcode Date: Fri, 13 Mar 2026 15:52:11 -0400 Subject: [PATCH 1/5] feat(docs): add advanced document tools (insertImage, insertTable, createHeaderFooter, addComment) --- workspace-server/src/index.ts | 89 ++++++ workspace-server/src/services/DocsService.ts | 318 ++++++++++++++++++- 2 files changed, 406 insertions(+), 1 deletion(-) diff --git a/workspace-server/src/index.ts b/workspace-server/src/index.ts index 674d678..5dd2b86 100644 --- a/workspace-server/src/index.ts +++ b/workspace-server/src/index.ts @@ -312,6 +312,95 @@ async function main() { docsService.replaceText, ); + server.registerTool( + 'docs.insertImage', + { + description: + 'Inserts an image into a Google Doc at a specified position or at the end of the document.', + inputSchema: { + documentId: z.string().describe('The ID of the document to modify.'), + imageUrl: z + .string() + .describe('The URL of the image to insert. Must be publicly accessible.'), + positionIndex: z + .number() + .optional() + .describe( + 'The index position to insert the image. If not provided, inserts at the end.', + ), + tabId: z + .string() + .optional() + .describe('The ID of the tab to modify. If not provided, modifies the first tab.'), + widthPt: z + .number() + .optional() + .describe('The width of the image in points (pt).'), + heightPt: z + .number() + .optional() + .describe('The height of the image in points (pt).'), + }, + }, + docsService.insertImage, + ); + + server.registerTool( + 'docs.insertTable', + { + description: + 'Inserts a table into a Google Doc at a specified position or at the end of the document.', + inputSchema: { + documentId: z.string().describe('The ID of the document to modify.'), + rows: z.number().min(1).describe('The number of rows in the table.'), + columns: z.number().min(1).describe('The number of columns in the table.'), + tabId: z + .string() + .optional() + .describe('The ID of the tab to modify. If not provided, modifies the first tab.'), + positionIndex: z + .number() + .optional() + .describe( + 'The index position to insert the table. If not provided, inserts at the end.', + ), + }, + }, + docsService.insertTable, + ); + + server.registerTool( + 'docs.createHeaderFooter', + { + description: + 'Creates a header or footer in a Google Doc with optional initial text.', + inputSchema: { + documentId: z.string().describe('The ID of the document to modify.'), + type: z + .enum(['header', 'footer']) + .describe('The type of element to create: "header" or "footer".'), + text: z + .string() + .optional() + .describe('Optional text to insert into the header or footer.'), + }, + }, + docsService.createHeaderFooter, + ); + + server.registerTool( + 'docs.addComment', + { + description: + 'Adds a comment to a Google Doc. The comment will appear in the document\'s comment thread.', + inputSchema: { + documentId: z.string().describe('The ID of the document to comment on.'), + content: z.string().describe('The text content of the comment.'), + }, + }, + docsService.addComment, + ); + server.registerTool( 'docs.formatText', { diff --git a/workspace-server/src/services/DocsService.ts b/workspace-server/src/services/DocsService.ts index ea55591..6f68d27 100644 --- a/workspace-server/src/services/DocsService.ts +++ b/workspace-server/src/services/DocsService.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { google, docs_v1 } from 'googleapis'; +import { google, docs_v1, drive_v3 } from 'googleapis'; import { AuthManager } from '../auth/AuthManager'; import { logToFile } from '../utils/logger'; import { extractDocId } from '../utils/IdUtils'; @@ -65,6 +65,12 @@ export class DocsService { return google.docs({ version: 'v1', ...options }); } + private async getDriveClient(): Promise { + const auth = await this.authManager.getAuthenticatedClient(); + const options = { ...gaxiosOptions, auth }; + return google.drive({ version: 'v3', ...options }); + } + public getSuggestions = async ({ documentId }: { documentId: string }) => { logToFile( `[DocsService] Starting getSuggestions for document: ${documentId}`, @@ -649,6 +655,316 @@ export class DocsService { } }; + public insertImage = async ({ + documentId, + imageUrl, + positionIndex, + tabId, + widthPt, + heightPt, + }: { + documentId: string; + imageUrl: string; + positionIndex?: number; + tabId?: string; + widthPt?: number; + heightPt?: number; + }) => { + logToFile( + `[DocsService] Starting insertImage for document: ${documentId}, tabId: ${tabId}`, + ); + try { + const id = extractDocId(documentId) || documentId; + const docs = await this.getDocsClient(); + + let insertIndex = positionIndex; + if (!insertIndex) { + const res = await docs.documents.get({ + documentId: id, + fields: 'tabs', + includeTabsContent: true, + }); + const tabs = res.data.tabs || []; + let content: docs_v1.Schema$StructuralElement[] | undefined; + if (tabId) { + const tab = tabs.find((t) => t.tabProperties?.tabId === tabId); + if (!tab) { + throw new Error(`Tab with ID ${tabId} not found.`); + } + content = tab.documentTab?.body?.content; + } else if (tabs.length > 0) { + content = tabs[0].documentTab?.body?.content; + } + const lastElement = content?.[content.length - 1]; + const endIndex = lastElement?.endIndex || 2; + insertIndex = Math.max(1, endIndex - 1); + } + + const imageRequest: docs_v1.Schema$Request = { + insertInlineImage: { + uri: imageUrl, + location: { + index: insertIndex, + tabId, + }, + }, + }; + + if (widthPt || heightPt) { + imageRequest.insertInlineImage!.objectSize = { + width: widthPt + ? { magnitude: widthPt, unit: 'PT' } + : { magnitude: 300, unit: 'PT' }, + height: heightPt + ? { magnitude: heightPt, unit: 'PT' } + : { magnitude: 200, unit: 'PT' }, + }; + } + + const update = await docs.documents.batchUpdate({ + documentId: id, + requestBody: { + requests: [imageRequest], + }, + }); + + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify({ + documentId: update.data.documentId, + insertedAt: insertIndex, + }), + }, + ], + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + logToFile(`[DocsService] Error during docs.insertImage: ${errorMessage}`); + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify({ error: errorMessage }), + }, + ], + }; + } + }; + + public insertTable = async ({ + documentId, + rows, + columns, + tabId, + positionIndex, + }: { + documentId: string; + rows: number; + columns: number; + tabId?: string; + positionIndex?: number; + }) => { + logToFile( + `[DocsService] Starting insertTable for document: ${documentId}, tabId: ${tabId}`, + ); + try { + const id = extractDocId(documentId) || documentId; + const docs = await this.getDocsClient(); + + let insertIndex = positionIndex; + if (!insertIndex) { + const res = await docs.documents.get({ + documentId: id, + fields: 'tabs', + includeTabsContent: true, + }); + const tabs = res.data.tabs || []; + let content: docs_v1.Schema$StructuralElement[] | undefined; + if (tabId) { + const tab = tabs.find((t) => t.tabProperties?.tabId === tabId); + if (!tab) { + throw new Error(`Tab with ID ${tabId} not found.`); + } + content = tab.documentTab?.body?.content; + } else if (tabs.length > 0) { + content = tabs[0].documentTab?.body?.content; + } + const lastElement = content?.[content.length - 1]; + const endIndex = lastElement?.endIndex || 2; + insertIndex = Math.max(1, endIndex - 1); + } + + const update = await docs.documents.batchUpdate({ + documentId: id, + requestBody: { + requests: [ + { + insertTable: { + rows, + columns, + location: { + index: insertIndex, + tabId, + }, + }, + }, + ], + }, + }); + + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify({ + documentId: update.data.documentId, + rows, + columns, + insertedAt: insertIndex, + }), + }, + ], + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + logToFile(`[DocsService] Error during docs.insertTable: ${errorMessage}`); + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify({ error: errorMessage }), + }, + ], + }; + } + }; + + public createHeaderFooter = async ({ + documentId, + type, + text, + }: { + documentId: string; + type: 'header' | 'footer'; + text?: string; + }) => { + logToFile( + `[DocsService] Starting createHeaderFooter for document: ${documentId}, type: ${type}`, + ); + try { + const id = extractDocId(documentId) || documentId; + const docs = await this.getDocsClient(); + + const createRequest: docs_v1.Schema$Request = + type === 'header' + ? ({ createHeader: { type: 'DEFAULT' } } as docs_v1.Schema$Request) + : ({ createFooter: { type: 'DEFAULT' } } as docs_v1.Schema$Request); + + const createResult = await docs.documents.batchUpdate({ + documentId: id, + requestBody: { + requests: [createRequest], + }, + }); + + const segmentId = + type === 'header' + ? createResult.data.replies?.[0]?.createHeader?.headerId + : createResult.data.replies?.[0]?.createFooter?.footerId; + + if (text && segmentId) { + await docs.documents.batchUpdate({ + documentId: id, + requestBody: { + requests: [ + { + insertText: { + endOfSegmentLocation: { + segmentId, + }, + text, + }, + }, + ], + }, + }); + } + + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify({ + documentId: id, + type, + segmentId, + }), + }, + ], + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + logToFile( + `[DocsService] Error during docs.createHeaderFooter: ${errorMessage}`, + ); + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify({ error: errorMessage }), + }, + ], + }; + } + }; + + public addComment = async ({ + documentId, + content, + }: { + documentId: string; + content: string; + }) => { + logToFile(`[DocsService] Starting addComment for document: ${documentId}`); + try { + const id = extractDocId(documentId) || documentId; + const drive = await this.getDriveClient(); + const res = await drive.comments.create({ + fileId: id, + requestBody: { + content, + }, + fields: 'id,content,createdTime,author', + }); + + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify(res.data), + }, + ], + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + logToFile(`[DocsService] Error during docs.addComment: ${errorMessage}`); + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify({ error: errorMessage }), + }, + ], + }; + } + }; + private _readStructuralElement( element: docs_v1.Schema$StructuralElement, ): string { From 8677abd043f865129a9c3c0cd33c6b47a38bd11d Mon Sep 17 00:00:00 2001 From: oskarcode Date: Fri, 13 Mar 2026 17:02:32 -0400 Subject: [PATCH 2/5] fix: preserve aspect ratio for images, use consistent endIndex default --- workspace-server/src/services/DocsService.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/workspace-server/src/services/DocsService.ts b/workspace-server/src/services/DocsService.ts index 6f68d27..0a26232 100644 --- a/workspace-server/src/services/DocsService.ts +++ b/workspace-server/src/services/DocsService.ts @@ -696,7 +696,7 @@ export class DocsService { content = tabs[0].documentTab?.body?.content; } const lastElement = content?.[content.length - 1]; - const endIndex = lastElement?.endIndex || 2; + const endIndex = lastElement?.endIndex || 1; insertIndex = Math.max(1, endIndex - 1); } @@ -711,14 +711,14 @@ export class DocsService { }; if (widthPt || heightPt) { - imageRequest.insertInlineImage!.objectSize = { - width: widthPt - ? { magnitude: widthPt, unit: 'PT' } - : { magnitude: 300, unit: 'PT' }, - height: heightPt - ? { magnitude: heightPt, unit: 'PT' } - : { magnitude: 200, unit: 'PT' }, - }; + const objectSize: docs_v1.Schema$Size = {}; + if (widthPt) { + objectSize.width = { magnitude: widthPt, unit: 'PT' }; + } + if (heightPt) { + objectSize.height = { magnitude: heightPt, unit: 'PT' }; + } + imageRequest.insertInlineImage!.objectSize = objectSize; } const update = await docs.documents.batchUpdate({ @@ -793,7 +793,7 @@ export class DocsService { content = tabs[0].documentTab?.body?.content; } const lastElement = content?.[content.length - 1]; - const endIndex = lastElement?.endIndex || 2; + const endIndex = lastElement?.endIndex || 1; insertIndex = Math.max(1, endIndex - 1); } From 916606e74f911c24bffab04368538c6ea5abadc8 Mon Sep 17 00:00:00 2001 From: oskarcode Date: Fri, 13 Mar 2026 17:39:10 -0400 Subject: [PATCH 3/5] fix: improve image insertion and reduce code duplication - Add detailed logging for image dimension handling to preserve aspect ratio - Extract duplicate insertIndex calculation logic into reusable helper method - Consolidate identical logic between insertImage and insertTable methods - Add comprehensive documentation for aspect ratio preservation behavior - Improve code maintainability and reduce duplication --- workspace-server/src/services/DocsService.ts | 96 +++++++++++--------- 1 file changed, 52 insertions(+), 44 deletions(-) diff --git a/workspace-server/src/services/DocsService.ts b/workspace-server/src/services/DocsService.ts index 0a26232..c8385c3 100644 --- a/workspace-server/src/services/DocsService.ts +++ b/workspace-server/src/services/DocsService.ts @@ -118,6 +118,45 @@ export class DocsService { } }; + /** + * Calculates the appropriate insertion index for new content in a document. + * If positionIndex is provided, uses that. Otherwise, finds the end of the document content + * and calculates an appropriate insertion point. + */ + private async _calculateInsertionIndex( + documentId: string, + positionIndex?: number, + tabId?: string, + ): Promise { + if (positionIndex !== undefined) { + return positionIndex; + } + + const docs = await this.getDocsClient(); + const res = await docs.documents.get({ + documentId, + fields: 'tabs', + includeTabsContent: true, + }); + + const tabs = res.data.tabs || []; + let content: docs_v1.Schema$StructuralElement[] | undefined; + + if (tabId) { + const tab = tabs.find((t) => t.tabProperties?.tabId === tabId); + if (!tab) { + throw new Error(`Tab with ID ${tabId} not found.`); + } + content = tab.documentTab?.body?.content; + } else if (tabs.length > 0) { + content = tabs[0].documentTab?.body?.content; + } + + const lastElement = content?.[content.length - 1]; + const endIndex = lastElement?.endIndex || 1; + return Math.max(1, endIndex - 1); + } + private _extractSuggestions( body: docs_v1.Schema$Body | undefined | null, ): DocsSuggestion[] { @@ -677,28 +716,7 @@ export class DocsService { const id = extractDocId(documentId) || documentId; const docs = await this.getDocsClient(); - let insertIndex = positionIndex; - if (!insertIndex) { - const res = await docs.documents.get({ - documentId: id, - fields: 'tabs', - includeTabsContent: true, - }); - const tabs = res.data.tabs || []; - let content: docs_v1.Schema$StructuralElement[] | undefined; - if (tabId) { - const tab = tabs.find((t) => t.tabProperties?.tabId === tabId); - if (!tab) { - throw new Error(`Tab with ID ${tabId} not found.`); - } - content = tab.documentTab?.body?.content; - } else if (tabs.length > 0) { - content = tabs[0].documentTab?.body?.content; - } - const lastElement = content?.[content.length - 1]; - const endIndex = lastElement?.endIndex || 1; - insertIndex = Math.max(1, endIndex - 1); - } + const insertIndex = await this._calculateInsertionIndex(id, positionIndex, tabId); const imageRequest: docs_v1.Schema$Request = { insertInlineImage: { @@ -710,6 +728,8 @@ export class DocsService { }, }; + // Only set explicit dimensions if provided. When only one dimension is provided, + // Google Docs API will automatically preserve aspect ratio for the other dimension. if (widthPt || heightPt) { const objectSize: docs_v1.Schema$Size = {}; if (widthPt) { @@ -719,6 +739,15 @@ export class DocsService { objectSize.height = { magnitude: heightPt, unit: 'PT' }; } imageRequest.insertInlineImage!.objectSize = objectSize; + + // Log dimension info for debugging + if (widthPt && heightPt) { + logToFile(`[DocsService] Setting explicit dimensions: ${widthPt}pt x ${heightPt}pt`); + } else if (widthPt) { + logToFile(`[DocsService] Setting width: ${widthPt}pt (height will preserve aspect ratio)`); + } else { + logToFile(`[DocsService] Setting height: ${heightPt}pt (width will preserve aspect ratio)`); + } } const update = await docs.documents.batchUpdate({ @@ -774,28 +803,7 @@ export class DocsService { const id = extractDocId(documentId) || documentId; const docs = await this.getDocsClient(); - let insertIndex = positionIndex; - if (!insertIndex) { - const res = await docs.documents.get({ - documentId: id, - fields: 'tabs', - includeTabsContent: true, - }); - const tabs = res.data.tabs || []; - let content: docs_v1.Schema$StructuralElement[] | undefined; - if (tabId) { - const tab = tabs.find((t) => t.tabProperties?.tabId === tabId); - if (!tab) { - throw new Error(`Tab with ID ${tabId} not found.`); - } - content = tab.documentTab?.body?.content; - } else if (tabs.length > 0) { - content = tabs[0].documentTab?.body?.content; - } - const lastElement = content?.[content.length - 1]; - const endIndex = lastElement?.endIndex || 1; - insertIndex = Math.max(1, endIndex - 1); - } + const insertIndex = await this._calculateInsertionIndex(id, positionIndex, tabId); const update = await docs.documents.batchUpdate({ documentId: id, From e3bc2e3fed8bd811c0cfc57b0db159af2ee6e86a Mon Sep 17 00:00:00 2001 From: oskarcode Date: Wed, 18 Mar 2026 16:57:19 -0400 Subject: [PATCH 4/5] fix: remove docs.addComment to keep comment tools in drive namespace --- workspace-server/src/index.ts | 13 ----- workspace-server/src/services/DocsService.ts | 50 +------------------- 2 files changed, 1 insertion(+), 62 deletions(-) diff --git a/workspace-server/src/index.ts b/workspace-server/src/index.ts index 5dd2b86..85c986b 100644 --- a/workspace-server/src/index.ts +++ b/workspace-server/src/index.ts @@ -388,19 +388,6 @@ async function main() { docsService.createHeaderFooter, ); - server.registerTool( - 'docs.addComment', - { - description: - 'Adds a comment to a Google Doc. The comment will appear in the document\'s comment thread.', - inputSchema: { - documentId: z.string().describe('The ID of the document to comment on.'), - content: z.string().describe('The text content of the comment.'), - }, - }, - docsService.addComment, - ); - server.registerTool( 'docs.formatText', { diff --git a/workspace-server/src/services/DocsService.ts b/workspace-server/src/services/DocsService.ts index c8385c3..705460a 100644 --- a/workspace-server/src/services/DocsService.ts +++ b/workspace-server/src/services/DocsService.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { google, docs_v1, drive_v3 } from 'googleapis'; +import { google, docs_v1 } from 'googleapis'; import { AuthManager } from '../auth/AuthManager'; import { logToFile } from '../utils/logger'; import { extractDocId } from '../utils/IdUtils'; @@ -65,12 +65,6 @@ export class DocsService { return google.docs({ version: 'v1', ...options }); } - private async getDriveClient(): Promise { - const auth = await this.authManager.getAuthenticatedClient(); - const options = { ...gaxiosOptions, auth }; - return google.drive({ version: 'v3', ...options }); - } - public getSuggestions = async ({ documentId }: { documentId: string }) => { logToFile( `[DocsService] Starting getSuggestions for document: ${documentId}`, @@ -931,48 +925,6 @@ export class DocsService { } }; - public addComment = async ({ - documentId, - content, - }: { - documentId: string; - content: string; - }) => { - logToFile(`[DocsService] Starting addComment for document: ${documentId}`); - try { - const id = extractDocId(documentId) || documentId; - const drive = await this.getDriveClient(); - const res = await drive.comments.create({ - fileId: id, - requestBody: { - content, - }, - fields: 'id,content,createdTime,author', - }); - - return { - content: [ - { - type: 'text' as const, - text: JSON.stringify(res.data), - }, - ], - }; - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : String(error); - logToFile(`[DocsService] Error during docs.addComment: ${errorMessage}`); - return { - content: [ - { - type: 'text' as const, - text: JSON.stringify({ error: errorMessage }), - }, - ], - }; - } - }; - private _readStructuralElement( element: docs_v1.Schema$StructuralElement, ): string { From fadc793894127e43a82ff1076b1166415496c417 Mon Sep 17 00:00:00 2001 From: oskarcode Date: Mon, 23 Mar 2026 00:35:43 -0400 Subject: [PATCH 5/5] fix(docs): address review feedback for tab flattening and header/footer text safety --- workspace-server/src/index.ts | 1 + workspace-server/src/services/DocsService.ts | 45 ++++++++++++-------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/workspace-server/src/index.ts b/workspace-server/src/index.ts index 85c986b..e03fcf2 100644 --- a/workspace-server/src/index.ts +++ b/workspace-server/src/index.ts @@ -321,6 +321,7 @@ async function main() { documentId: z.string().describe('The ID of the document to modify.'), imageUrl: z .string() + .url() .describe('The URL of the image to insert. Must be publicly accessible.'), positionIndex: z .number() diff --git a/workspace-server/src/services/DocsService.ts b/workspace-server/src/services/DocsService.ts index 705460a..4e9abfc 100644 --- a/workspace-server/src/services/DocsService.ts +++ b/workspace-server/src/services/DocsService.ts @@ -114,8 +114,9 @@ export class DocsService { /** * Calculates the appropriate insertion index for new content in a document. - * If positionIndex is provided, uses that. Otherwise, finds the end of the document content - * and calculates an appropriate insertion point. + * If positionIndex is provided, uses that. Otherwise, finds the end of the + * target tab's content (or first tab when tabId is not provided) and + * calculates an appropriate insertion point. */ private async _calculateInsertionIndex( documentId: string, @@ -132,10 +133,10 @@ export class DocsService { fields: 'tabs', includeTabsContent: true, }); - - const tabs = res.data.tabs || []; + + const tabs = this._flattenTabs(res.data.tabs || []); let content: docs_v1.Schema$StructuralElement[] | undefined; - + if (tabId) { const tab = tabs.find((t) => t.tabProperties?.tabId === tabId); if (!tab) { @@ -145,9 +146,9 @@ export class DocsService { } else if (tabs.length > 0) { content = tabs[0].documentTab?.body?.content; } - + const lastElement = content?.[content.length - 1]; - const endIndex = lastElement?.endIndex || 1; + const endIndex = lastElement?.endIndex ?? 1; return Math.max(1, endIndex - 1); } @@ -296,6 +297,7 @@ export class DocsService { error instanceof Error ? error.message : String(error); logToFile(`Error during docs.create: ${errorMessage}`); return { + isError: true, content: [ { type: 'text' as const, @@ -722,25 +724,25 @@ export class DocsService { }, }; - // Only set explicit dimensions if provided. When only one dimension is provided, - // Google Docs API will automatically preserve aspect ratio for the other dimension. - if (widthPt || heightPt) { + // Only set explicit dimensions if provided. If only one dimension is + // supplied, API behavior for the missing dimension may vary. + if (widthPt !== undefined || heightPt !== undefined) { const objectSize: docs_v1.Schema$Size = {}; - if (widthPt) { + if (widthPt !== undefined) { objectSize.width = { magnitude: widthPt, unit: 'PT' }; } - if (heightPt) { + if (heightPt !== undefined) { objectSize.height = { magnitude: heightPt, unit: 'PT' }; } imageRequest.insertInlineImage!.objectSize = objectSize; - + // Log dimension info for debugging - if (widthPt && heightPt) { + if (widthPt !== undefined && heightPt !== undefined) { logToFile(`[DocsService] Setting explicit dimensions: ${widthPt}pt x ${heightPt}pt`); - } else if (widthPt) { - logToFile(`[DocsService] Setting width: ${widthPt}pt (height will preserve aspect ratio)`); + } else if (widthPt !== undefined) { + logToFile(`[DocsService] Setting width only: ${widthPt}pt`); } else { - logToFile(`[DocsService] Setting height: ${heightPt}pt (width will preserve aspect ratio)`); + logToFile(`[DocsService] Setting height only: ${heightPt}pt`); } } @@ -767,6 +769,7 @@ export class DocsService { error instanceof Error ? error.message : String(error); logToFile(`[DocsService] Error during docs.insertImage: ${errorMessage}`); return { + isError: true, content: [ { type: 'text' as const, @@ -835,6 +838,7 @@ export class DocsService { error instanceof Error ? error.message : String(error); logToFile(`[DocsService] Error during docs.insertTable: ${errorMessage}`); return { + isError: true, content: [ { type: 'text' as const, @@ -878,6 +882,12 @@ export class DocsService { ? createResult.data.replies?.[0]?.createHeader?.headerId : createResult.data.replies?.[0]?.createFooter?.footerId; + if (text && !segmentId) { + throw new Error( + `Created ${type} but could not retrieve its ID from the API response. The provided text was not inserted.`, + ); + } + if (text && segmentId) { await docs.documents.batchUpdate({ documentId: id, @@ -915,6 +925,7 @@ export class DocsService { `[DocsService] Error during docs.createHeaderFooter: ${errorMessage}`, ); return { + isError: true, content: [ { type: 'text' as const,