@@ -4,6 +4,7 @@ import { isPlainRecord } from '@sim/utils/object'
44import { mapWithConcurrency } from '@/lib/core/utils/concurrency'
55import { isPayloadSizeLimitError , readResponseJsonWithLimit } from '@/lib/core/utils/stream-limits'
66import { VALIDATE_RETRY_OPTIONS } from '@/lib/knowledge/documents/utils'
7+ import { getGmailMailboxEmail , getGmailProfile , gmailThreadUrl } from '@/connectors/gmail/mailbox'
78import { DEFAULT_MAX_THREADS , gmailConnectorMeta } from '@/connectors/gmail/meta'
89import { fetchGoogleApiWithRetry , GoogleApiError } from '@/connectors/google-workspace/api-errors'
910import {
@@ -621,34 +622,27 @@ async function resolveLabelNames(
621622 * Creates a lightweight document stub from a thread list entry.
622623 * Uses metadata-based contentHash for change detection without downloading content.
623624 */
624- function threadToStub (
625+ async function threadToStub (
625626 thread : GmailThread ,
627+ accessToken : string ,
626628 syncContext ?: Record < string , unknown >
627- ) : ExternalDocument {
629+ ) : Promise < ExternalDocument > {
630+ const mailboxEmail = await getGmailMailboxEmail ( accessToken , syncContext )
628631 return {
629632 externalId : memberDocumentId ( thread . id , syncContext ) ,
630633 title : thread . snippet || 'Untitled Thread' ,
631634 content : '' ,
632635 contentDeferred : true ,
633636 estimatedBytes : CONNECTOR_TEXT_DOCUMENT_MAX_BYTES ,
634637 mimeType : 'text/plain' ,
635- sourceUrl : threadUrl ( thread . id ) ,
638+ sourceUrl : gmailThreadUrl ( thread . id , mailboxEmail ) ,
636639 /** Rehydrate older rows that omitted separately stored message bodies. */
637640 contentHash : `gmail:${ thread . id } :${ thread . historyId } :body-v2` ,
638641 skippedRetryPolicy : 'source-change' ,
639642 metadata : { } ,
640643 }
641644}
642645
643- /**
644- * Deep link to a thread. `#all` is used rather than `#inbox` because a synced
645- * thread may be archived or live only under a user label, where an `#inbox`
646- * fragment resolves to nothing.
647- */
648- function threadUrl ( threadId : string ) : string {
649- return `https://mail.google.com/mail/u/0/#all/${ threadId } `
650- }
651-
652646/** A feed position: the mailbox history id the next read starts from, mid-page when paging. */
653647interface GmailChangeCursor {
654648 historyId : string
@@ -780,22 +774,7 @@ const gmailMailboxConnector: ConnectorConfig = {
780774 if ( syncContext ?. mirrorsSourceAcls === true ) {
781775 throw new Error ( 'Company-wide Gmail indexing uses complete mailbox listings' )
782776 }
783- const response = await fetchGoogleApiWithRetry (
784- 'gmail.users.getProfile' ,
785- `${ GMAIL_API_BASE } /profile?fields=historyId` ,
786- {
787- method : 'GET' ,
788- headers : { Authorization : `Bearer ${ accessToken } ` , Accept : 'application/json' } ,
789- }
790- )
791- const data : unknown = await response . json ( )
792- if (
793- ! isPlainRecord ( data ) ||
794- typeof data . historyId !== 'string' ||
795- ! / ^ \d + $ / . test ( data . historyId )
796- ) {
797- throw new Error ( 'Gmail returned malformed profile metadata' )
798- }
777+ const data = await getGmailProfile ( accessToken , syncContext )
799778 return JSON . stringify ( { historyId : data . historyId } )
800779 } ,
801780
@@ -813,7 +792,7 @@ const gmailMailboxConnector: ConnectorConfig = {
813792 accessToken : string ,
814793 sourceConfig : Record < string , unknown > ,
815794 cursor : string ,
816- syncContext ? : Record < string , unknown >
795+ syncContext : Record < string , unknown > = { }
817796 ) : Promise < ExternalChangeList > => {
818797 if ( syncContext ?. mirrorsSourceAcls === true ) {
819798 throw new Error ( 'Company-wide Gmail indexing uses complete mailbox listings' )
@@ -854,7 +833,11 @@ const gmailMailboxConnector: ConnectorConfig = {
854833 const externalId = memberDocumentId ( threadId , syncContext )
855834 const thread = await fetchThread ( accessToken , threadId , 'metadata' )
856835 if ( ! thread || ! threadInScope ( thread , scope ) ) return { kind : 'removed' , externalId }
857- return { kind : 'upsert' , externalId, document : threadToStub ( thread , syncContext ) }
836+ return {
837+ kind : 'upsert' ,
838+ externalId,
839+ document : await threadToStub ( thread , accessToken , syncContext ) ,
840+ }
858841 }
859842 )
860843
@@ -873,7 +856,7 @@ const gmailMailboxConnector: ConnectorConfig = {
873856 accessToken : string ,
874857 sourceConfig : Record < string , unknown > ,
875858 cursor ?: string ,
876- syncContext ? : Record < string , unknown >
859+ syncContext : Record < string , unknown > = { }
877860 ) : Promise < ExternalDocumentList > => {
878861 const signal = syncContext ?. signal instanceof AbortSignal ? syncContext . signal : undefined
879862 signal ?. throwIfAborted ( )
@@ -962,7 +945,7 @@ const gmailMailboxConnector: ConnectorConfig = {
962945 const metadata = thread . historyId
963946 ? thread
964947 : await fetchThread ( accessToken , thread . id , 'minimal' , signal )
965- return metadata ? threadToStub ( metadata , syncContext ) : null
948+ return metadata ? threadToStub ( metadata , accessToken , syncContext ) : null
966949 } )
967950 const documents = stubs . filter ( ( stub ) : stub is ExternalDocument => stub !== null )
968951
@@ -1002,7 +985,7 @@ const gmailMailboxConnector: ConnectorConfig = {
1002985 accessToken : string ,
1003986 _sourceConfig : Record < string , unknown > ,
1004987 externalId : string ,
1005- syncContext ? : Record < string , unknown >
988+ syncContext : Record < string , unknown > = { }
1006989 ) : Promise < ExternalDocument | null > => {
1007990 const signal = syncContext ?. signal instanceof AbortSignal ? syncContext . signal : undefined
1008991 signal ?. throwIfAborted ( )
@@ -1028,7 +1011,7 @@ const gmailMailboxConnector: ConnectorConfig = {
10281011 }
10291012 return {
10301013 ...markSkipped (
1031- threadToStub ( after , syncContext ) ,
1014+ await threadToStub ( after , accessToken , syncContext ) ,
10321015 sizeLimitSkipReason ( MAX_THREAD_RESPONSE_BYTES )
10331016 ) ,
10341017 skippedExistingDisposition : 'replace' ,
@@ -1043,7 +1026,10 @@ const gmailMailboxConnector: ConnectorConfig = {
10431026 } catch ( error ) {
10441027 if ( error instanceof ConnectorFileTooLargeError ) {
10451028 return {
1046- ...markSkipped ( threadToStub ( thread , syncContext ) , sizeLimitSkipReason ( error . limitBytes ) ) ,
1029+ ...markSkipped (
1030+ await threadToStub ( thread , accessToken , syncContext ) ,
1031+ sizeLimitSkipReason ( error . limitBytes )
1032+ ) ,
10471033 skippedExistingDisposition : 'replace' ,
10481034 }
10491035 }
@@ -1056,7 +1042,7 @@ const gmailMailboxConnector: ConnectorConfig = {
10561042 metadata . labels = await resolveLabelNames ( accessToken , labelIds , syncContext )
10571043
10581044 return {
1059- ...threadToStub ( thread , syncContext ) ,
1045+ ...( await threadToStub ( thread , accessToken , syncContext ) ) ,
10601046 title : subject ,
10611047 content,
10621048 contentDeferred : false ,
0 commit comments