Uh oh!
There was an error while loading. Please reload this page.
feat(sync): add Composio Outlook memory-sync pipeline - #136
Conversation
Add OutlookSyncPipeline for the Composio `outlook` toolkit (Microsoft Outlook mail), modeled on the message-shaped Gmail pipeline. - Verified action slug OUTLOOK_LIST_MESSAGES (openhuman OUTLOOK_CURATED). - Newest-first via Graph `top`/`orderby`; server-side depth via `$filter` on receivedDateTime, cursor preferred over the horizon. - Stable upsert key `outlook:<id>`; taint `external_sync`. - Registered in the three sync mod files; mock coverage asserts 2-page pagination, cursor persistence, and idempotent re-tick. Refs tinyhumansai#99
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughWalkthroughThis PR adds an incremental Outlook synchronization pipeline for Composio. It supports paginated message fetching, date filtering, cursor-based deduplication, skip-token normalization, document conversion, public exports, and mock-based integration tests. ChangesOutlook Sync Pipeline
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Sequence Diagram(s)sequenceDiagram
participant Caller
participant OutlookSyncPipeline
participant ComposioClient
participant OutlookAPI
Caller->>OutlookSyncPipeline: tick()
OutlookSyncPipeline->>ComposioClient: Request messages with cursor and skip token
ComposioClient->>OutlookAPI: Fetch page
OutlookAPI-->>ComposioClient: Messages and next link
ComposioClient-->>OutlookSyncPipeline: Return response payload
OutlookSyncPipeline->>OutlookSyncPipeline: Deduplicate and build documents
OutlookSyncPipeline-->>Caller: Return synchronized documents and cursor
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/memory/sync/composio/providers/outlook.rs (1)
30-44: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDocument the public constructors.
Add item documentation for
newandwith_limits. State thatwith_limitsclamps each value below one to one.Proposed documentation
impl OutlookSyncPipeline { + /// Creates an Outlook synchronization pipeline for one Composio connection. pub fn new(client: ComposioClient, connection_id: impl Into<String>) -> Self { Self { client, connection_id: connection_id.into(), max_pages: 10, page_size: 25, } } + /// Sets the maximum page count and page size.+ ///+ /// Values below one are clamped to one. pub fn with_limits(mut self, max_pages: usize, page_size: usize) -> Self {As per coding guidelines, “Document public APIs, module contracts, and non-obvious behavior thoroughly, preferring module-level docs and item docs.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/memory/sync/composio/providers/outlook.rs` around lines 30 - 44, Add item-level documentation to the public OutlookSyncPipeline constructors new and with_limits, describing their purpose and parameters. Explicitly document that with_limits clamps max_pages and page_size values below one to one, while preserving the existing implementation.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/memory/sync/composio/providers/outlook.rs`:
- Around line 30-44: Add item-level documentation to the public
OutlookSyncPipeline constructors new and with_limits, describing their purpose
and parameters. Explicitly document that with_limits clamps max_pages and
page_size values below one to one, while preserving the existing implementation.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7a247310-4072-48a4-ba6f-2f835243621d
📒 Files selected for processing (5)
src/memory/sync/composio/mod.rssrc/memory/sync/composio/providers/mod.rssrc/memory/sync/composio/providers/outlook.rssrc/memory/sync/mod.rstests/composio_sync_mock.rs
|
| Filename | Overview |
|---|---|
| src/memory/sync/composio/providers/outlook.rs | New OutlookSyncPipeline implementing IncrementalSource; normalize_skip_token correctly extracts bare skip tokens from full Graph @odata.nextLink URLs, cursor/filter field consistency is clean, and all methods follow established pipeline patterns. |
| tests/composio_sync_mock.rs | Adds outlook_paginates_persists_cursor_and_is_idempotent integration test; mock now uses a realistic full Graph @odata.nextLink URL, verifying normalize_skip_token extracts the bare token for the page-2 request. |
| src/memory/sync/composio/mod.rs | Additive re-export of OutlookSyncPipeline; no structural changes. |
| src/memory/sync/composio/providers/mod.rs | Additive module declaration and re-export; alphabetical insertion is correct. |
| src/memory/sync/mod.rs | Additive public re-export of OutlookSyncPipeline into the memory::sync namespace. |
Sequence Diagram
sequenceDiagram
participant Caller
participant OutlookSyncPipeline
participant run_incremental_sync
participant ComposioClient
participant SyncState
Caller->>OutlookSyncPipeline: tick(config, context)
OutlookSyncPipeline->>run_incremental_sync: delegate
run_incremental_sync->>SyncState: "load(toolkit=outlook, connection_id)"
run_incremental_sync->>ComposioClient: "execute(OUTLOOK_LIST_MESSAGES, {top, orderby, filter?})"
ComposioClient-->>run_incremental_sync: "{data: {value:[...], @odata.nextLink: https://...?$skiptoken=XYZ}}"
run_incremental_sync->>OutlookSyncPipeline: extract_page(data)
Note over OutlookSyncPipeline: normalize_skip_token reduces full URL to bare XYZ
OutlookSyncPipeline-->>run_incremental_sync: "PageFetch{items, next=Some(XYZ)}"
run_incremental_sync->>ComposioClient: "execute(OUTLOOK_LIST_MESSAGES, {top, orderby, filter?, skip_token=XYZ})"
ComposioClient-->>run_incremental_sync: "{data: {value:[]}} no nextLink"
run_incremental_sync->>SyncState: advance_cursor(newest receivedDateTime)
run_incremental_sync->>SyncState: save()
run_incremental_sync-->>Caller: "SyncOutcome{records_ingested, actions_called}"
Reviews (3): Last reviewed commit: "Merge main into feat/composio-outlook-sy..." | Re-trigger Greptile
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Address Greptile review on tinyhumansai#136. - Graph returns `@odata.nextLink` as a full URL; feeding that back verbatim as the paging arg would not resume pagination and would silently cap large mailboxes at one page. Reduce it to the bare `$skiptoken` value (pass-through when Composio already surfaces a bare token). The mock test now uses a real Graph nextLink URL so page two is only reached via the extracted token. - Drop the `lastModifiedDateTime` fallback from sort_cursor: the persisted cursor feeds a `receivedDateTime ge <cursor>` filter, so a cursor taken from a different field could skip valid messages on the next sync. The cursor now always comes from receivedDateTime, matching the filter.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/memory/sync/composio/providers/outlook.rs (1)
91-112: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winUse the
OUTLOOK_LIST_MESSAGESschema for incremental sync.
- Send
orderbyas an array and usereceived_date_time_gefor the cursor or depth filter.- Replace unsupported
filterandskip_tokenarguments withpage_token, passingnext_page_tokenthrough unchanged.- Update
normalize_skip_tokenand the mock to modelnext_page_tokenand/arguments/page_token.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/memory/sync/composio/providers/outlook.rs` around lines 91 - 112, The Outlook incremental-sync request construction in `outlook.rs` must match the `OUTLOOK_LIST_MESSAGES` schema: send `orderby` as an array, use `received_date_time_ge` for cursor and depth filters, and replace `skip_token` with `page_token`, passing `next_page_token` through unchanged. Update `normalize_skip_token` and the mock in `tests/composio_sync_mock.rs` to model `next_page_token` and `/arguments/page_token` consistently; apply these changes at the listed Outlook provider and mock sites.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/memory/sync/composio/providers/outlook.rs`:
- Around line 91-112: The Outlook incremental-sync request construction in
`outlook.rs` must match the `OUTLOOK_LIST_MESSAGES` schema: send `orderby` as an
array, use `received_date_time_ge` for cursor and depth filters, and replace
`skip_token` with `page_token`, passing `next_page_token` through unchanged.
Update `normalize_skip_token` and the mock in `tests/composio_sync_mock.rs` to
model `next_page_token` and `/arguments/page_token` consistently; apply these
changes at the listed Outlook provider and mock sites.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: cd44df11-331e-4fc5-b360-d6ac220ef17e
📒 Files selected for processing (2)
src/memory/sync/composio/providers/outlook.rstests/composio_sync_mock.rs
Resolve conflicts from Composio Google Calendar/Drive (tinyhumansai#134) and Docs/Sheets (tinyhumansai#135) landing alongside the Outlook sync pipeline: - union the pipeline re-exports in sync/composio/mod.rs and sync/mod.rs - keep both the Outlook and Google Calendar/Drive integration tests in tests/composio_sync_mock.rs (independent additions git interleaved) All 17 composio_sync_mock tests pass.
…-sync # Conflicts: # src/memory/sync/composio/mod.rs # src/memory/sync/mod.rs # tests/composio_sync_mock.rs
There was a problem hiding this comment.
YellowSnnowmann has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
Uh oh!
There was an error while loading. Please reload this page.
Summary
Adds
OutlookSyncPipelinefor the Composiooutlooktoolkit (Microsoft Outlook mail), message-shaped, modeled on the Gmail pipeline. Previously advertised but unsyncable (#106).OUTLOOK_LIST_MESSAGES(verified againstcatalogs_productivity.rs).receivedDateTimecursor,filterdepth window. Stable upsert keyoutlook:<id>,taint = external_sync, content-free logging.providers/mod.rs,composio/mod.rs,sync/mod.rs.Disclosed assumption: the catalog pins the slug but not arg schemas, so the pagination token key (
skip_token) and depthfilterkey are documented// NOTE:assumptions using Microsoft Graph's native parameter names. Pagination is driven off the response-side next-token (@odata.nextLink) read via multiple pointer fallbacks, so a wrong request-side key degrades gracefully to a single-page fetch — never an error or data corruption.Pipeline body only (step 1 of 2); openhuman wiring closes the issue end to end.
API Or Behavior Changes
One new public
SyncPipelinetype exported frommemory::sync. Additive.Tests
cargo fmt --checkcargo clippy --all-targets -- -D warningscargo build --all-targetscargo test(all-features green; new mock test drives 2-page pagination via the real token key, asserts stabledocument_id, cursor persistence, idempotent re-sync)Documentation
Module/item docs including the arg-assumption notes. No external docs needed.
Part of #99 · tracker #106
Summary by CodeRabbit
New Features
Bug Fixes
Tests