Skip to content

Commit 801f442

Browse files
committed
fix(outlook): allow calendar paging without re-supplying the time window
Cursor Bugbot round: startDateTime/endDateTime were required tool params, so a paging call carrying only pageToken failed validateToolParameters before the request was built — even though the url builder short-circuits on pageToken and ignores both bounds. Relax them to optional and enforce the real invariant (pageToken OR both bounds) in the url builder, matching tools/sharepoint/list_sites.ts. Block subblocks stay required, so the normal editor flow is unchanged. Also correct the calendar_respond comment: Graph documents exactly two 400 conditions for accept/decline, both on proposedNewTime, which we never send. A non-empty comment alongside sendResponse=false is valid.
1 parent 4abde7e commit 801f442

4 files changed

Lines changed: 41 additions & 10 deletions

File tree

apps/sim/tools/outlook/calendar-tools.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,19 @@ describe('outlook calendar tools', () => {
150150
)
151151
})
152152

153-
it('omits the comment key when empty so sendResponse=false is accepted by Graph', () => {
153+
it('requires both window bounds unless paging with a pageToken', () => {
154+
const url = outlookCalendarListEventsTool.request.url as (p: unknown) => string
155+
// Paging supplies only the token: the window is already baked into the nextLink.
156+
expect(url({ accessToken: 't', pageToken: 'https://graph.microsoft.com/next' })).toBe(
157+
'https://graph.microsoft.com/next'
158+
)
159+
expect(() => url({ accessToken: 't', startDateTime: '2025-06-03T00:00:00Z' })).toThrow(
160+
/startDateTime and endDateTime are required/
161+
)
162+
expect(() => url({ accessToken: 't' })).toThrow(/startDateTime and endDateTime are required/)
163+
})
164+
165+
it('omits the comment key when empty but keeps it alongside sendResponse=false', () => {
154166
const body = outlookCalendarRespondTool.request.body as (p: unknown) => Record<string, unknown>
155167
// Empty/whitespace comment with sendResponse off: comment must NOT be present.
156168
const noComment = body({
@@ -165,6 +177,12 @@ describe('outlook calendar tools', () => {
165177
const nullComment = body({ eventId: 'e1', responseType: 'decline', sendResponse: false })
166178
expect('comment' in nullComment).toBe(false)
167179

180+
// A real comment with sendResponse=false is valid: Graph's documented 400s for
181+
// accept/decline are both about proposedNewTime, which we never send.
182+
expect(
183+
body({ eventId: 'e1', responseType: 'decline', sendResponse: false, comment: 'Conflict' })
184+
).toEqual({ sendResponse: false, comment: 'Conflict' })
185+
168186
// A real comment is included, and sendResponse defaults to true.
169187
const withComment = body({ eventId: 'e1', responseType: 'accept', comment: 'See you there' })
170188
expect(withComment).toEqual({ sendResponse: true, comment: 'See you there' })

apps/sim/tools/outlook/calendar_list_events.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,22 @@ export const outlookCalendarListEventsTool: ToolConfig<
4545
visibility: 'user-only',
4646
description: 'ID of the calendar to read. Defaults to the mailbox default calendar.',
4747
},
48+
// Not `required`, because a paging call supplies only `pageToken` and the window bounds
49+
// are baked into the nextLink. The url builder enforces "pageToken OR both bounds".
50+
// Mirrors tools/sharepoint/list_sites.ts.
4851
startDateTime: {
4952
type: 'string',
50-
required: true,
53+
required: false,
5154
visibility: 'user-or-llm',
5255
description:
53-
'Start of the time window (ISO 8601, e.g. 2025-06-03T00:00:00-08:00). Interpreted as UTC if no offset is given.',
56+
'Start of the time window (ISO 8601, e.g. 2025-06-03T00:00:00-08:00). Interpreted as UTC if no offset is given. Required unless paging with pageToken.',
5457
},
5558
endDateTime: {
5659
type: 'string',
57-
required: true,
60+
required: false,
5861
visibility: 'user-or-llm',
5962
description:
60-
'End of the time window (ISO 8601, e.g. 2025-06-10T00:00:00-08:00). Interpreted as UTC if no offset is given.',
63+
'End of the time window (ISO 8601, e.g. 2025-06-10T00:00:00-08:00). Interpreted as UTC if no offset is given. Required unless paging with pageToken.',
6164
},
6265
maxResults: {
6366
type: 'number',
@@ -88,6 +91,12 @@ export const outlookCalendarListEventsTool: ToolConfig<
8891
return assertGraphNextPageUrl(params.pageToken.trim())
8992
}
9093

94+
if (!params.startDateTime?.trim() || !params.endDateTime?.trim()) {
95+
throw new Error(
96+
'startDateTime and endDateTime are required to list calendar events (calendarView needs both bounds). Supply pageToken instead to continue a previous page.'
97+
)
98+
}
99+
91100
const requested = Number(params.maxResults)
92101
const maxResults = Number.isFinite(requested)
93102
? Math.max(1, Math.min(Math.abs(requested), MAX_EVENTS_PER_PAGE))

apps/sim/tools/outlook/calendar_respond.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,11 @@ export const outlookCalendarRespondTool: ToolConfig<
9090
}
9191
},
9292
body: (params) => {
93-
// Graph rejects sendResponse=false when a `comment` key is present at all
94-
// (even ""): "'SendResponse' must be true when 'Comment' is not null." So
95-
// only include comment when it's actually non-empty.
93+
// Only send `comment` when it has content — an empty string is meaningless to the
94+
// organizer. Graph documents exactly two 400 conditions for accept/decline, both on
95+
// `proposedNewTime` (which we never send); `comment` carries no such restriction and
96+
// is valid alongside sendResponse=false.
97+
// https://learn.microsoft.com/en-us/graph/api/event-decline
9698
const body: Record<string, unknown> = {
9799
sendResponse: toBool(params.sendResponse, true),
98100
}

apps/sim/tools/outlook/types.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -710,8 +710,10 @@ export interface OutlookCalendarListEventsParams {
710710
accessToken: string
711711
/** Calendar to read. Omit for the mailbox's default calendar. */
712712
calendarId?: string
713-
startDateTime: string
714-
endDateTime: string
713+
/** Required unless `pageToken` is supplied, which already encodes the window. */
714+
startDateTime?: string
715+
/** Required unless `pageToken` is supplied, which already encodes the window. */
716+
endDateTime?: string
715717
maxResults?: number
716718
orderBy?: string
717719
/** Full `@odata.nextLink` URL from a previous page. */

0 commit comments

Comments
 (0)