1- import { fetchGraphJson , type GraphResponse } from "./graph.js" ;
1+ import { fetchGraphJson , postGraphJson , type GraphResponse } from "./graph.js" ;
22
33export type GraphThreadMessage = {
44 id ?: string ;
@@ -18,6 +18,16 @@ type GraphPagedResponse<T> = GraphResponse<T> & {
1818 "@odata.nextLink" ?: string ;
1919} ;
2020
21+ type GraphBatchResponse < T > = {
22+ responses ?: Array < {
23+ id ?: string ;
24+ status ?: number ;
25+ body ?: T ;
26+ } > ;
27+ } ;
28+
29+ const GRAPH_BATCH_MAX_REQUESTS = 20 ;
30+
2131// Graph team ids are Azure AD group ids. We keep this check intentionally broad
2232// because some tenants surface uppercase GUIDs and Graph ids are the only raw
2333// team ids we can safely reuse without another directory lookup.
@@ -38,6 +48,45 @@ function graphPathFromNextLink(nextLink: string): string | null {
3848 }
3949}
4050
51+ function chunkArray < T > ( values : T [ ] , size : number ) : T [ ] [ ] {
52+ const chunks : T [ ] [ ] = [ ] ;
53+ for ( let index = 0 ; index < values . length ; index += size ) {
54+ chunks . push ( values . slice ( index , index + size ) ) ;
55+ }
56+ return chunks ;
57+ }
58+
59+ async function findGraphTeamIdByPrimaryChannel ( params : {
60+ token : string ;
61+ conversationTeamId : string ;
62+ candidateTeamIds : string [ ] ;
63+ } ) : Promise < string | null > {
64+ for ( const teamIds of chunkArray ( params . candidateTeamIds , GRAPH_BATCH_MAX_REQUESTS ) ) {
65+ const response = await postGraphJson < GraphBatchResponse < { id ?: string } > > ( {
66+ token : params . token ,
67+ path : "/$batch" ,
68+ body : {
69+ requests : teamIds . map ( ( graphTeamId , index ) => ( {
70+ id : String ( index ) ,
71+ method : "GET" ,
72+ url : `/teams/${ encodeURIComponent ( graphTeamId ) } /primaryChannel?$select=id` ,
73+ } ) ) ,
74+ } ,
75+ } ) ;
76+ const responses = new Map (
77+ ( response . responses ?? [ ] ) . map ( ( entry ) => [ entry . id ?? "" , entry ] as const ) ,
78+ ) ;
79+ for ( const [ index , graphTeamId ] of teamIds . entries ( ) ) {
80+ const candidate = responses . get ( String ( index ) ) ;
81+ const primaryId = candidate ?. status === 200 ? candidate . body ?. id ?. trim ( ) : undefined ;
82+ if ( primaryId && primaryId === params . conversationTeamId ) {
83+ return graphTeamId ;
84+ }
85+ }
86+ }
87+ return null ;
88+ }
89+
4190/**
4291 * Strip HTML tags from Teams message content, preserving @mention display names.
4392 * Teams wraps mentions in <at>Name</at> tags.
@@ -104,28 +153,19 @@ export async function resolveTeamGroupId(
104153 let path = `/groups?$filter=${ encodeURIComponent ( "resourceProvisioningOptions/Any(x:x eq 'Team')" ) } &$select=id&$top=999` ;
105154 while ( path ) {
106155 const teams = await fetchGraphJson < GraphPagedResponse < { id ?: string } > > ( { token, path } ) ;
107- for ( const candidate of teams . value ?? [ ] ) {
108- const graphTeamId = candidate . id ?. trim ( ) ;
109- if ( ! graphTeamId ) {
110- continue ;
111- }
112- try {
113- const primary = await fetchGraphJson < { id ?: string } > ( {
114- token,
115- path : `/teams/${ encodeURIComponent ( graphTeamId ) } /primaryChannel?$select=id` ,
116- } ) ;
117- const primaryId = primary . id ?. trim ( ) ;
118- if ( ! primaryId || primaryId !== conversationTeamId ) {
119- continue ;
120- }
121- teamGroupIdCache . set ( conversationTeamId , {
122- groupId : graphTeamId ,
123- expiresAt : Date . now ( ) + CACHE_TTL_MS ,
124- } ) ;
125- return graphTeamId ;
126- } catch {
127- continue ;
128- }
156+ const graphTeamId = await findGraphTeamIdByPrimaryChannel ( {
157+ token,
158+ conversationTeamId,
159+ candidateTeamIds : ( teams . value ?? [ ] )
160+ . map ( ( candidate ) => candidate . id ?. trim ( ) )
161+ . filter ( ( value ) : value is string => Boolean ( value ) ) ,
162+ } ) ;
163+ if ( graphTeamId ) {
164+ teamGroupIdCache . set ( conversationTeamId , {
165+ groupId : graphTeamId ,
166+ expiresAt : Date . now ( ) + CACHE_TTL_MS ,
167+ } ) ;
168+ return graphTeamId ;
129169 }
130170 path = teams [ "@odata.nextLink" ]
131171 ? ( graphPathFromNextLink ( teams [ "@odata.nextLink" ] ) ?? "" )
0 commit comments