@@ -9,6 +9,7 @@ import type {
99 IFluidCodeDetails ,
1010 IContainerPolicies ,
1111} from "@fluidframework/container-definitions/internal" ;
12+ import { LoaderHeader } from "@fluidframework/container-definitions/internal" ;
1213import type {
1314 FluidObject ,
1415 IConfigProviderBase ,
@@ -20,10 +21,44 @@ import type {
2021 IDocumentServiceFactory ,
2122 IUrlResolver ,
2223} from "@fluidframework/driver-definitions/internal" ;
24+ import { DriverHeader } from "@fluidframework/driver-definitions/internal" ;
25+ import {
26+ GenericError ,
27+ normalizeError ,
28+ createChildMonitoringContext ,
29+ mixinMonitoringContext ,
30+ sessionStorageConfigProvider ,
31+ PerformanceEvent ,
32+ isFluidError ,
33+ } from "@fluidframework/telemetry-utils/internal" ;
34+ import { v4 as uuid } from "uuid" ;
2335
36+ import { DebugLogger } from "./debugLogger.js" ;
2437import { createFrozenDocumentServiceFactory } from "./frozenServices.js" ;
2538import { Loader } from "./loader.js" ;
39+ import { pkgVersion } from "./packageVersion.js" ;
2640import type { ProtocolHandlerBuilder } from "./protocol.js" ;
41+ import type {
42+ LoadSummarizerSummaryResult ,
43+ OnDemandSummaryResults ,
44+ SummarizeOnDemandResults ,
45+ } from "./summarizerResultTypes.js" ;
46+
47+ interface OnDemandSummarizeResultsPromises {
48+ readonly summarySubmitted : Promise < SummarizeOnDemandResults [ "summarySubmitted" ] > ;
49+ readonly summaryOpBroadcasted : Promise < SummarizeOnDemandResults [ "summaryOpBroadcasted" ] > ;
50+ }
51+
52+ interface OnDemandSummarizeOptions {
53+ readonly reason ?: string ;
54+ readonly retryOnFailure ?: boolean ;
55+ readonly fullTree ?: boolean ;
56+ }
57+
58+ interface SummarizerLike {
59+ readonly ISummarizer ?: SummarizerLike ;
60+ summarizeOnDemand ( options : OnDemandSummarizeOptions ) : OnDemandSummarizeResultsPromises ;
61+ }
2762
2863/**
2964 * Properties necessary for creating and loading a container.
@@ -103,6 +138,15 @@ export interface ILoadExistingContainerProps extends ICreateAndLoadContainerProp
103138 readonly pendingLocalState ?: string | undefined ;
104139}
105140
141+ /**
142+ * Props used to load summarizer container.
143+ * @legacy @alpha
144+ */
145+ export type ILoadSummarizerContainerProps = Omit <
146+ ILoadExistingContainerProps ,
147+ "pendingLocalState"
148+ > ;
149+
106150/**
107151 * Props used to create a detached container.
108152 * @legacy @beta
@@ -200,3 +244,133 @@ export async function loadFrozenContainerFromPendingState(
200244 documentServiceFactory : createFrozenDocumentServiceFactory ( props . documentServiceFactory ) ,
201245 } ) ;
202246}
247+
248+ /**
249+ * Loads a summarizer container with the required headers, triggers an on-demand summary, and then closes it.
250+ * Returns success/failure and an optional error for host-side handling.
251+ *
252+ * @legacy @alpha
253+ */
254+ export async function loadSummarizerContainerAndMakeSummary (
255+ loadSummarizerContainerProps : ILoadSummarizerContainerProps ,
256+ ) : Promise < LoadSummarizerSummaryResult > {
257+ const { logger, configProvider, request : originalRequest } = loadSummarizerContainerProps ;
258+ const telemetryProps = {
259+ loaderId : uuid ( ) ,
260+ loaderVersion : pkgVersion ,
261+ } ;
262+
263+ const subMc = mixinMonitoringContext (
264+ DebugLogger . mixinDebugLogger ( "fluid:telemetry" , logger , {
265+ all : telemetryProps ,
266+ } ) ,
267+ sessionStorageConfigProvider . value ,
268+ configProvider ,
269+ ) ;
270+ const mc = createChildMonitoringContext ( {
271+ logger : subMc . logger ,
272+ namespace : "SummarizerOnDemand" ,
273+ } ) ;
274+ return PerformanceEvent . timedExecAsync (
275+ mc . logger ,
276+ { eventName : "SummarizerOnDemandSummary" } ,
277+ async ( event ) => {
278+ const baseHeaders = originalRequest . headers ;
279+ const request = {
280+ ...originalRequest ,
281+ headers : {
282+ ...baseHeaders ,
283+ [ LoaderHeader . cache ] : false ,
284+ [ LoaderHeader . clientDetails ] : {
285+ capabilities : { interactive : false } ,
286+ type : "summarizer" ,
287+ } ,
288+ [ DriverHeader . summarizingClient ] : true ,
289+ [ LoaderHeader . reconnect ] : false ,
290+ } ,
291+ } ;
292+
293+ const container = await loadExistingContainer ( {
294+ ...loadSummarizerContainerProps ,
295+ request,
296+ } ) ;
297+
298+ let summarySubmitted : SummarizeOnDemandResults [ "summarySubmitted" ] ;
299+ let summaryOpBroadcasted : SummarizeOnDemandResults [ "summaryOpBroadcasted" ] ;
300+ try {
301+ if ( container . getEntryPoint === undefined ) {
302+ throw new GenericError ( "container.getEntryPoint() is undefined" ) ;
303+ }
304+ const fluidObject = ( await container . getEntryPoint ( ) ) as FluidObject < SummarizerLike > ;
305+ const summarizer = fluidObject ?. ISummarizer ;
306+ if ( summarizer === undefined ) {
307+ throw new GenericError ( "Summarizer entry point not available" ) ;
308+ }
309+ // Host controlled feature gate for fullTree
310+ // Default value will be false
311+ const fullTreeGate =
312+ mc . config . getBoolean ( "Fluid.Summarizer.FullTree.OnDemand" ) === true ;
313+
314+ const summarizeResults : OnDemandSummarizeResultsPromises =
315+ summarizer . summarizeOnDemand ( {
316+ reason : "summaryOnRequest" ,
317+ retryOnFailure : true ,
318+ fullTree : fullTreeGate ,
319+ } ) ;
320+ [ summarySubmitted , summaryOpBroadcasted ] = await Promise . all ( [
321+ summarizeResults . summarySubmitted ,
322+ summarizeResults . summaryOpBroadcasted ,
323+ ] ) ;
324+
325+ const summaryResults : OnDemandSummaryResults = {
326+ summarySubmitted : summarySubmitted . success ,
327+ summaryInfo : summarySubmitted . success
328+ ? {
329+ stage : summarySubmitted . data . stage ,
330+ handle : summaryOpBroadcasted . success
331+ ? summaryOpBroadcasted . data . summarizeOp . contents . handle
332+ : undefined ,
333+ }
334+ : { } ,
335+ summaryOpBroadcasted : summaryOpBroadcasted . success ,
336+ } ;
337+
338+ if ( summarySubmitted . success && summaryOpBroadcasted . success ) {
339+ event . end ( {
340+ success : true ,
341+ summarySubmitted : true ,
342+ summaryOpBroadcasted : true ,
343+ } ) ;
344+ return {
345+ success : true ,
346+ summaryResults,
347+ } ;
348+ }
349+
350+ const failureError =
351+ summarySubmitted . success === false
352+ ? summarySubmitted . error
353+ : summaryOpBroadcasted . success === false
354+ ? summaryOpBroadcasted . error
355+ : new GenericError ( "On demand summary failed" ) ;
356+
357+ event . end ( {
358+ success : false ,
359+ summarySubmitted : summarySubmitted . success ,
360+ summaryOpBroadcasted : summaryOpBroadcasted . success ,
361+ } ) ;
362+ return {
363+ success : false ,
364+ error : failureError ,
365+ } ;
366+ } catch ( error ) {
367+ event . cancel ( { success : false } , error ) ;
368+ const caughtError = isFluidError ( error ) ? error : normalizeError ( error ) ;
369+ return { success : false , error : caughtError } ;
370+ } finally {
371+ container . dispose ( ) ;
372+ }
373+ } ,
374+ { start : true , end : true , cancel : "generic" } ,
375+ ) ;
376+ }
0 commit comments