Skip to content

Commit 4a5b2c6

Browse files
authored
fix(google): close post-merge verification gaps from the Workspace sweep (#9954)
* fix(google): close post-merge verification gaps from the Workspace sweep - Sheets V2 Append gets its own Value Input Option field so a value stored for Write/Update no longer reaches Append - Sheets legacy read guards the spreadsheet id; the COLUMNS check only fires when a filter actually runs - Calendar reminders accept the API's {overrides} object; updatedMin notes deleted events are included - Drive search skips orderBy for fullText queries, which Drive rejects - Docs number parsing ignores booleans; Tasks trims a blank task list id to @default - Forms trigger respondentEmail marked optional; Slides block exposes layout/notes/master properties * fix(google): keep reminder useDefault, detect fullText outside quoted values, accept numeric task list ids * fix(google): require a boolean useDefault in reminder objects
1 parent 51e21ae commit 4a5b2c6

12 files changed

Lines changed: 70 additions & 20 deletions

File tree

‎apps/docs/content/docs/integrations/google_calendar.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ List events from Google Calendar.
111111
| `pageToken` | string | No | Token for retrieving the next page of results |
112112
| `orderBy` | string | No | Order of events: startTime \(chronological, the default\) or updated \(last-modified\). startTime is always valid here because singleEvents is set. |
113113
| `timeZone` | string | No | IANA time zone used for times in the response \(e.g., America/New_York\). Defaults to the calendar's time zone. |
114-
| `updatedMin` | string | No | Only return events modified at or after this time \(RFC3339 timestamp, e.g., 2025-06-03T00:00:00Z\) |
114+
| `updatedMin` | string | No | Only return events modified at or after this time \(RFC3339 timestamp, e.g., 2025-06-03T00:00:00Z\). Events deleted since then are always included. |
115115

116116
#### Output
117117

‎apps/docs/content/docs/integrations/google_drive.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ Search for files in Google Drive using advanced query syntax (e.g., fullText con
529529
| `query` | string | Yes | Google Drive query string using advanced search syntax \(e.g., "fullText contains 'budget'", "mimeType = 'application/pdf'", "modifiedTime > '2024-01-01'"\) |
530530
| `pageSize` | number | No | Maximum number of files to return \(default: 100\) |
531531
| `pageToken` | string | No | Token for fetching the next page of results |
532-
| `orderBy` | string | No | Comma-separated sort keys: createdTime, folder, modifiedByMeTime, modifiedTime, name, name_natural, quotaBytesUsed, recency, sharedWithMeTime, starred, viewedByMeTime. Append " desc" to reverse \(e.g., "modifiedTime desc"\) |
532+
| `orderBy` | string | No | Comma-separated sort keys: createdTime, folder, modifiedByMeTime, modifiedTime, name, name_natural, quotaBytesUsed, recency, sharedWithMeTime, starred, viewedByMeTime. Append " desc" to reverse \(e.g., "modifiedTime desc"\). Ignored for fullText queries, which Drive always orders by relevance |
533533
| `driveId` | string | No | ID of a shared drive to restrict results to \(default: search My Drive and all shared drives\) |
534534

535535
#### Output

‎apps/sim/blocks/blocks/google_sheets.ts‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,17 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`,
622622
{ label: 'User Entered (Parse formulas)', id: 'USER_ENTERED' },
623623
{ label: "Raw (Don't parse formulas)", id: 'RAW' },
624624
],
625-
condition: { field: 'operation', value: ['write', 'update', 'append', 'batch_update'] },
625+
condition: { field: 'operation', value: ['write', 'update', 'batch_update'] },
626+
},
627+
{
628+
id: 'appendValueInputOption',
629+
title: 'Value Input Option',
630+
type: 'dropdown',
631+
options: [
632+
{ label: 'User Entered (Parse formulas)', id: 'USER_ENTERED' },
633+
{ label: "Raw (Don't parse formulas)", id: 'RAW' },
634+
],
635+
condition: { field: 'operation', value: 'append' },
626636
},
627637
{
628638
id: 'includeValuesInResponse',
@@ -937,6 +947,7 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`,
937947
filterValue,
938948
filterMatchType,
939949
majorDimension,
950+
appendValueInputOption,
940951
...rest
941952
} = params
942953

@@ -1067,6 +1078,9 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`,
10671078
? { filterValue: filterValue as string }
10681079
: {}),
10691080
...(filterMatchType ? { filterMatchType: filterMatchType as string } : {}),
1081+
...(operation === 'append'
1082+
? { valueInputOption: appendValueInputOption || undefined }
1083+
: {}),
10701084
}
10711085
},
10721086
},
@@ -1079,6 +1093,7 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`,
10791093
cellRange: { type: 'string', description: 'Cell range (e.g., A1:D10)' },
10801094
values: { type: 'string', description: 'Cell values data' },
10811095
valueInputOption: { type: 'string', description: 'Value input option' },
1096+
appendValueInputOption: { type: 'string', description: 'Value input option for Append' },
10821097
insertDataOption: { type: 'string', description: 'Data insertion option' },
10831098
includeValuesInResponse: {
10841099
type: 'boolean',

‎apps/sim/blocks/blocks/google_slides.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3479,6 +3479,19 @@ Return ONLY the text content - no explanations, no markdown formatting markers,
34793479
type: 'json',
34803480
description: 'Page properties from Get Page (pageBackgroundFill, colorScheme)',
34813481
},
3482+
layoutProperties: {
3483+
type: 'json',
3484+
description:
3485+
'Layout properties from Get Page, for layout pages (masterObjectId, name, displayName)',
3486+
},
3487+
notesProperties: {
3488+
type: 'json',
3489+
description: 'Notes properties from Get Page, for notes pages (speakerNotesObjectId)',
3490+
},
3491+
masterProperties: {
3492+
type: 'json',
3493+
description: 'Master properties from Get Page, for master pages (displayName)',
3494+
},
34823495
pageText: { type: 'string', description: 'Text on the page from Get Page' },
34833496
speakerNotes: { type: 'string', description: 'Speaker notes of the slide from Get Page' },
34843497
deleted: { type: 'boolean', description: 'Whether object was deleted' },

‎apps/sim/tools/generated/tool-metadata.ts‎

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

‎apps/sim/tools/google_calendar/list.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export const listTool: ToolConfig<GoogleCalendarListParams, GoogleCalendarListRe
9090
required: false,
9191
visibility: 'user-or-llm',
9292
description:
93-
'Only return events modified at or after this time (RFC3339 timestamp, e.g., 2025-06-03T00:00:00Z)',
93+
'Only return events modified at or after this time (RFC3339 timestamp, e.g., 2025-06-03T00:00:00Z). Events deleted since then are always included.',
9494
},
9595
},
9696

‎apps/sim/tools/google_calendar/utils.ts‎

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { generateId } from '@sim/utils/id'
2+
import { isRecordLike } from '@sim/utils/object'
23
import type {
34
GoogleCalendarApiEventResponse,
45
GoogleCalendarEventDetails,
@@ -104,13 +105,14 @@ const MAX_REMINDER_OVERRIDES = 5
104105
const MAX_REMINDER_MINUTES = 40320
105106

106107
/**
107-
* Parse reminder overrides from a JSON string or array of `{ method, minutes }` objects.
108-
* Returns `undefined` when nothing usable was provided. An empty array (`[]`) means
109-
* "no reminders" and is returned as-is so callers can turn the calendar default off.
108+
* Parse the reminders input into the Calendar `reminders` setting. Accepts a JSON string or
109+
* value that is either an array of `{ method, minutes }` overrides (custom reminders; `[]`
110+
* turns reminders off) or the API's own `{ useDefault, overrides }` object, so a previous
111+
* event's reminders can be copied as-is. Returns `undefined` when nothing usable was provided.
110112
*/
111-
function parseReminderOverrides(
112-
reminders: string | GoogleCalendarReminderOverride[] | undefined
113-
): GoogleCalendarReminderOverride[] | undefined {
113+
function parseReminders(
114+
reminders: unknown
115+
): { useDefault: boolean; overrides?: GoogleCalendarReminderOverride[] } | undefined {
114116
if (reminders === undefined || reminders === null) return undefined
115117

116118
let parsed: unknown = reminders
@@ -124,6 +126,19 @@ function parseReminderOverrides(
124126
}
125127
}
126128

129+
if (isRecordLike(parsed) && !Array.isArray(parsed)) {
130+
if (typeof parsed.useDefault !== 'boolean') {
131+
throw new Error('Reminders object needs a boolean useDefault, like {"useDefault":true}')
132+
}
133+
if (parsed.useDefault) return { useDefault: true }
134+
const overrides = parsed.overrides === undefined ? [] : parsed.overrides
135+
return { useDefault: false, overrides: validateReminderOverrides(overrides) }
136+
}
137+
138+
return { useDefault: false, overrides: validateReminderOverrides(parsed) }
139+
}
140+
141+
function validateReminderOverrides(parsed: unknown): GoogleCalendarReminderOverride[] {
127142
if (!Array.isArray(parsed)) {
128143
throw new Error('Reminders must be an array of { method, minutes } objects')
129144
}
@@ -168,8 +183,8 @@ export function applyEventOptions(
168183
if (params.transparency) body.transparency = params.transparency
169184
const colorId = params.colorId?.toString().trim()
170185
if (colorId) body.colorId = colorId
171-
const overrides = parseReminderOverrides(params.reminders)
172-
if (overrides) body.reminders = { useDefault: false, overrides }
186+
const reminders = parseReminders(params.reminders)
187+
if (reminders) body.reminders = reminders
173188
}
174189

175190
/** Fields shared by every event-shaped output beyond the core id/summary/time/people fields. */

‎apps/sim/tools/google_docs/utils.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function resolveTabId(tabId: unknown): string | undefined {
3333
* string (agent tool-calls). Returns `undefined` when absent or not finite.
3434
*/
3535
export function parseOptionalNumber(value: unknown): number | undefined {
36-
if (value === undefined || value === null) return undefined
36+
if (value === undefined || value === null || typeof value === 'boolean') return undefined
3737
if (typeof value === 'string' && value.trim() === '') return undefined
3838
const parsed = Number(value)
3939
return Number.isFinite(parsed) ? parsed : undefined

‎apps/sim/tools/google_drive/search.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export const searchTool: ToolConfig<GoogleDriveSearchParams, GoogleDriveSearchRe
5959
required: false,
6060
visibility: 'user-or-llm',
6161
description:
62-
'Comma-separated sort keys: createdTime, folder, modifiedByMeTime, modifiedTime, name, name_natural, quotaBytesUsed, recency, sharedWithMeTime, starred, viewedByMeTime. Append " desc" to reverse (e.g., "modifiedTime desc")',
62+
'Comma-separated sort keys: createdTime, folder, modifiedByMeTime, modifiedTime, name, name_natural, quotaBytesUsed, recency, sharedWithMeTime, starred, viewedByMeTime. Append " desc" to reverse (e.g., "modifiedTime desc"). Ignored for fullText queries, which Drive always orders by relevance',
6363
},
6464
driveId: {
6565
type: 'string',
@@ -84,7 +84,8 @@ export const searchTool: ToolConfig<GoogleDriveSearchParams, GoogleDriveSearchRe
8484
url.searchParams.append('supportsAllDrives', 'true')
8585
url.searchParams.append('includeItemsFromAllDrives', 'true')
8686
const orderBy = params.orderBy?.trim()
87-
if (orderBy) {
87+
const queryWithoutLiterals = (params.query ?? '').replace(/'(?:[^'\\]|\\.)*'/g, "''")
88+
if (orderBy && !/\bfullText\b/.test(queryWithoutLiterals)) {
8889
url.searchParams.append('orderBy', orderBy)
8990
}
9091

‎apps/sim/tools/google_sheets/read.ts‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ export const readTool: ToolConfig<GoogleSheetsToolParams, GoogleSheetsReadRespon
5656
// Keep a generous column/row bound to avoid huge payloads
5757
if (!params.range) {
5858
const defaultRange = 'A1:Z1000'
59-
return `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${encodeURIComponent(defaultRange)}`
59+
return `https://sheets.googleapis.com/v4/spreadsheets/${safeUrlPathSegment(spreadsheetId, 'spreadsheetId')}/values/${encodeURIComponent(defaultRange)}`
6060
}
6161

6262
// Otherwise, get values from the specified range
63-
return `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${encodeURIComponent(params.range)}`
63+
return `https://sheets.googleapis.com/v4/spreadsheets/${safeUrlPathSegment(spreadsheetId, 'spreadsheetId')}/values/${encodeURIComponent(params.range)}`
6464
},
6565
method: 'GET',
6666
headers: (params) => {
@@ -193,7 +193,12 @@ export const readV2Tool: ToolConfig<GoogleSheetsV2ToolParams, GoogleSheetsV2Read
193193
throw new Error('Sheet name is required')
194194
}
195195

196-
if (params.majorDimension === 'COLUMNS' && params.filterColumn?.trim()) {
196+
if (
197+
params.majorDimension === 'COLUMNS' &&
198+
params.filterColumn?.trim() &&
199+
params.filterValue !== undefined &&
200+
params.filterValue !== ''
201+
) {
197202
throw new Error(
198203
'Filtering requires majorDimension ROWS; remove majorDimension or the filter'
199204
)

0 commit comments

Comments
 (0)