Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
6f26935
define ISummaryConfigurationWithSummaryOnRequest
WillieHabi Sep 3, 2025
baa9abb
skip election process and heuristics if "summaryOnRequest"
WillieHabi Sep 3, 2025
5d7cd2b
first implementation
WillieHabi Sep 4, 2025
9c16d45
preliminary test
WillieHabi Sep 4, 2025
2154e7f
NoCompat
WillieHabi Sep 4, 2025
7496a16
remove comment
WillieHabi Sep 4, 2025
0930506
format
WillieHabi Sep 4, 2025
4d60882
remove 'as const'
WillieHabi Sep 5, 2025
f837647
update comment
WillieHabi Sep 5, 2025
3bb5562
Merge branch 'main' into on-demand-summarizer
WillieHabi Sep 22, 2025
c4171ee
IFluidErrorBase
WillieHabi Sep 22, 2025
664f24a
extract props
WillieHabi Sep 22, 2025
701f909
monitoring context
WillieHabi Sep 22, 2025
da10f5a
telemetry names updated
WillieHabi Sep 22, 2025
5089777
format
WillieHabi Sep 22, 2025
25ec64d
test changes: test if summary is actually submitted
WillieHabi Sep 23, 2025
83ab5a6
end to end test
WillieHabi Sep 24, 2025
d11c0cd
widen ISummaryConfiguration type
WillieHabi Sep 24, 2025
56231c3
export types from loader
WillieHabi Sep 24, 2025
15ac1a8
aqueduct back compat
WillieHabi Sep 25, 2025
5baacbd
api report
WillieHabi Sep 25, 2025
b6526f0
Revert "api report"
WillieHabi Sep 25, 2025
d9ef086
Merge branch 'main' into on-demand-summarizer
WillieHabi Sep 25, 2025
ea42728
api report
WillieHabi Sep 25, 2025
a230f5b
export changes
WillieHabi Sep 29, 2025
ed91e6c
Merge branch 'main' into on-demand-summarizer
WillieHabi Sep 29, 2025
b70a8c1
issummaryonrequest
WillieHabi Sep 29, 2025
1447659
fix container runtime changes
WillieHabi Sep 29, 2025
904025a
pnpm-lock
WillieHabi Sep 29, 2025
1a4fc0b
fix circular dependency
WillieHabi Sep 29, 2025
82da27d
fix layer check
WillieHabi Sep 29, 2025
757eb0d
clean up API surface
WillieHabi Sep 30, 2025
9dbd56c
fix tests
WillieHabi Sep 30, 2025
947b32a
tag legacy api
WillieHabi Sep 30, 2025
bd9e8c6
documentation
WillieHabi Oct 1, 2025
dee5162
return IErrorBase
WillieHabi Oct 6, 2025
51bee2e
remove url from telemetry
WillieHabi Oct 6, 2025
d1c2080
remove url from telemetry
WillieHabi Oct 7, 2025
f3b0ae4
getBoolean
WillieHabi Oct 7, 2025
912ae5d
throw if getEntryPoint is undefined
WillieHabi Oct 7, 2025
c756060
legacy alpha
WillieHabi Oct 7, 2025
ad04394
fix telemetry tests
WillieHabi Oct 7, 2025
0d52933
remove summaryTree
WillieHabi Oct 7, 2025
f7a32e0
don't wait for connected
WillieHabi Oct 7, 2025
196308d
format
WillieHabi Oct 7, 2025
14352af
remove receivedSummaryAck
WillieHabi Oct 7, 2025
683fcd1
skip if not odsp
WillieHabi Oct 7, 2025
c4c96e8
simplify telemetry test
WillieHabi Oct 8, 2025
2a4b3d2
don't skip tests
WillieHabi Oct 8, 2025
497ee37
don't normalize caughtError
WillieHabi Oct 8, 2025
fbc5d6d
use loadExistingContainer function instead of Loader class
WillieHabi Oct 8, 2025
f3c0fe9
ILoadSummarizerContainerProps
WillieHabi Oct 8, 2025
0f8f176
rename input parameter
WillieHabi Oct 8, 2025
c592510
tests: fails gracefully when summary upload throws, on-demand summay …
WillieHabi Oct 9, 2025
36946c1
Merge branch 'main' into on-demand-summarizer
WillieHabi Oct 9, 2025
546b2a9
remove unused optional fail attrs
WillieHabi Oct 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion packages/framework/aqueduct/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,14 @@
"typescript": "~5.4.5"
},
"typeValidation": {
"broken": {},
"broken": {
"Interface_BaseContainerRuntimeFactoryProps": {
"backCompat": false
},
"Interface_ContainerRuntimeFactoryWithDefaultDataStoreProps": {
"backCompat": false
}
},
"entrypoint": "legacy"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ declare type old_as_current_for_Interface_BaseContainerRuntimeFactoryProps = req
* typeValidation.broken:
* "Interface_BaseContainerRuntimeFactoryProps": {"backCompat": false}
*/
// @ts-expect-error compatibility expected to be broken
declare type current_as_old_for_Interface_BaseContainerRuntimeFactoryProps = requireAssignableTo<TypeOnly<current.BaseContainerRuntimeFactoryProps>, TypeOnly<old.BaseContainerRuntimeFactoryProps>>

/*
Expand All @@ -265,6 +266,7 @@ declare type old_as_current_for_Interface_ContainerRuntimeFactoryWithDefaultData
* typeValidation.broken:
* "Interface_ContainerRuntimeFactoryWithDefaultDataStoreProps": {"backCompat": false}
*/
// @ts-expect-error compatibility expected to be broken
declare type current_as_old_for_Interface_ContainerRuntimeFactoryWithDefaultDataStoreProps = requireAssignableTo<TypeOnly<current.ContainerRuntimeFactoryWithDefaultDataStoreProps>, TypeOnly<old.ContainerRuntimeFactoryWithDefaultDataStoreProps>>

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ export interface ILoadFrozenContainerFromPendingStateProps extends ILoadExisting
readonly pendingLocalState: string;
}

// @alpha @legacy
export type ILoadSummarizerContainerProps = Omit<ILoadExistingContainerProps, "pendingLocalState">;

// @beta @legacy
export interface IParsedUrl {
id: string;
Expand Down Expand Up @@ -178,6 +181,28 @@ export function loadExistingContainer(loadExistingContainerProps: ILoadExistingC
// @alpha @legacy
export function loadFrozenContainerFromPendingState(props: ILoadFrozenContainerFromPendingStateProps): Promise<IContainer>;

// @alpha @legacy
export function loadSummarizerContainerAndMakeSummary(loadSummarizerContainerProps: ILoadSummarizerContainerProps): Promise<LoadSummarizerSummaryResult>;

// @alpha @legacy
export type LoadSummarizerSummaryResult = {
readonly success: true;
readonly summaryResults: OnDemandSummaryResults;
} | {
readonly success: false;
readonly error: IErrorBase;
};

// @alpha @legacy
export interface OnDemandSummaryResults {
readonly summaryInfo: {
readonly stage?: SummaryStage;
readonly handle?: string;
};
readonly summaryOpBroadcasted: boolean;
readonly summarySubmitted: boolean;
}

// @beta @legacy
export type ProtocolHandlerBuilder = (attributes: IDocumentAttributes, snapshot: IQuorumSnapshot, sendProposal: (key: string, value: any) => number) => IProtocolHandler;

Expand All @@ -196,6 +221,9 @@ export function rehydrateDetachedContainer(rehydrateDetachedContainerProps: IReh
// @beta @legacy
export function resolveWithLocationRedirectionHandling<T>(api: (request: IRequest) => Promise<T>, request: IRequest, urlResolver: IUrlResolver, logger?: ITelemetryBaseLogger): Promise<T>;

// @alpha @legacy
export type SummaryStage = "base" | "generate" | "upload" | "submit" | "unknown";

// @beta @legacy
export function tryParseCompatibleResolvedUrl(url: string): IParsedUrl | undefined;

Expand Down
174 changes: 174 additions & 0 deletions packages/loader/container-loader/src/createAndLoadContainerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
IFluidCodeDetails,
IContainerPolicies,
} from "@fluidframework/container-definitions/internal";
import { LoaderHeader } from "@fluidframework/container-definitions/internal";
import type {
FluidObject,
IConfigProviderBase,
Expand All @@ -20,10 +21,44 @@ import type {
IDocumentServiceFactory,
IUrlResolver,
} from "@fluidframework/driver-definitions/internal";
import { DriverHeader } from "@fluidframework/driver-definitions/internal";
import {
GenericError,
normalizeError,
createChildMonitoringContext,
mixinMonitoringContext,
sessionStorageConfigProvider,
PerformanceEvent,
isFluidError,
} from "@fluidframework/telemetry-utils/internal";
import { v4 as uuid } from "uuid";

import { DebugLogger } from "./debugLogger.js";
import { createFrozenDocumentServiceFactory } from "./frozenServices.js";
import { Loader } from "./loader.js";
import { pkgVersion } from "./packageVersion.js";
import type { ProtocolHandlerBuilder } from "./protocol.js";
import type {
LoadSummarizerSummaryResult,
OnDemandSummaryResults,
SummarizeOnDemandResults,
} from "./summarizerResultTypes.js";

interface OnDemandSummarizeResultsPromises {
readonly summarySubmitted: Promise<SummarizeOnDemandResults["summarySubmitted"]>;
readonly summaryOpBroadcasted: Promise<SummarizeOnDemandResults["summaryOpBroadcasted"]>;
}

interface OnDemandSummarizeOptions {
readonly reason?: string;
readonly retryOnFailure?: boolean;
readonly fullTree?: boolean;
}

interface SummarizerLike {
readonly ISummarizer?: SummarizerLike;
summarizeOnDemand(options: OnDemandSummarizeOptions): OnDemandSummarizeResultsPromises;
}

/**
* Properties necessary for creating and loading a container.
Expand Down Expand Up @@ -103,6 +138,15 @@ export interface ILoadExistingContainerProps extends ICreateAndLoadContainerProp
readonly pendingLocalState?: string | undefined;
}

/**
* Props used to load summarizer container.
* @legacy @alpha
*/
export type ILoadSummarizerContainerProps = Omit<
ILoadExistingContainerProps,
"pendingLocalState"
>;

/**
* Props used to create a detached container.
* @legacy @beta
Expand Down Expand Up @@ -200,3 +244,133 @@ export async function loadFrozenContainerFromPendingState(
documentServiceFactory: createFrozenDocumentServiceFactory(props.documentServiceFactory),
});
}

/**
* Loads a summarizer container with the required headers, triggers an on-demand summary, and then closes it.
* Returns success/failure and an optional error for host-side handling.
*
* @legacy @alpha
*/
export async function loadSummarizerContainerAndMakeSummary(
loadSummarizerContainerProps: ILoadSummarizerContainerProps,
): Promise<LoadSummarizerSummaryResult> {
const { logger, configProvider, request: originalRequest } = loadSummarizerContainerProps;
const telemetryProps = {
loaderId: uuid(),
loaderVersion: pkgVersion,
};

const subMc = mixinMonitoringContext(
DebugLogger.mixinDebugLogger("fluid:telemetry", logger, {
all: telemetryProps,
}),
sessionStorageConfigProvider.value,
configProvider,
);
const mc = createChildMonitoringContext({
logger: subMc.logger,
namespace: "SummarizerOnDemand",
});
return PerformanceEvent.timedExecAsync(
mc.logger,
{ eventName: "SummarizerOnDemandSummary" },
async (event) => {
const baseHeaders = originalRequest.headers;
const request = {
...originalRequest,
headers: {
...baseHeaders,
[LoaderHeader.cache]: false,
[LoaderHeader.clientDetails]: {
capabilities: { interactive: false },
type: "summarizer",
},
[DriverHeader.summarizingClient]: true,
[LoaderHeader.reconnect]: false,
},
};

const container = await loadExistingContainer({
...loadSummarizerContainerProps,
request,
});

let summarySubmitted: SummarizeOnDemandResults["summarySubmitted"];
let summaryOpBroadcasted: SummarizeOnDemandResults["summaryOpBroadcasted"];
try {
if (container.getEntryPoint === undefined) {
throw new GenericError("container.getEntryPoint() is undefined");
}
const fluidObject = (await container.getEntryPoint()) as FluidObject<SummarizerLike>;
const summarizer = fluidObject?.ISummarizer;
if (summarizer === undefined) {
throw new GenericError("Summarizer entry point not available");
}
// Host controlled feature gate for fullTree
// Default value will be false
const fullTreeGate =
mc.config.getBoolean("Fluid.Summarizer.FullTree.OnDemand") === true;

const summarizeResults: OnDemandSummarizeResultsPromises =
summarizer.summarizeOnDemand({
reason: "summaryOnRequest",
retryOnFailure: true,
fullTree: fullTreeGate,
});
[summarySubmitted, summaryOpBroadcasted] = await Promise.all([
summarizeResults.summarySubmitted,
summarizeResults.summaryOpBroadcasted,
]);

const summaryResults: OnDemandSummaryResults = {
summarySubmitted: summarySubmitted.success,
summaryInfo: summarySubmitted.success
? {
stage: summarySubmitted.data.stage,
handle: summaryOpBroadcasted.success
? summaryOpBroadcasted.data.summarizeOp.contents.handle
: undefined,
}
: {},
summaryOpBroadcasted: summaryOpBroadcasted.success,
};

if (summarySubmitted.success && summaryOpBroadcasted.success) {
event.end({
success: true,
summarySubmitted: true,
summaryOpBroadcasted: true,
});
return {
success: true,
summaryResults,
};
}

const failureError =
summarySubmitted.success === false
? summarySubmitted.error
: summaryOpBroadcasted.success === false
? summaryOpBroadcasted.error
: new GenericError("On demand summary failed");

event.end({
success: false,
summarySubmitted: summarySubmitted.success,
summaryOpBroadcasted: summaryOpBroadcasted.success,
});
return {
success: false,
error: failureError,
};
} catch (error) {
event.cancel({ success: false }, error);
const caughtError = isFluidError(error) ? error : normalizeError(error);
return { success: false, error: caughtError };
} finally {
container.dispose();
}
},
{ start: true, end: true, cancel: "generic" },
);
}
7 changes: 7 additions & 0 deletions packages/loader/container-loader/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,19 @@ export {
loadExistingContainer,
rehydrateDetachedContainer,
loadFrozenContainerFromPendingState,
loadSummarizerContainerAndMakeSummary,
type ICreateAndLoadContainerProps,
type ICreateDetachedContainerProps,
type ILoadExistingContainerProps,
type ILoadSummarizerContainerProps,
type IRehydrateDetachedContainerProps,
type ILoadFrozenContainerFromPendingStateProps,
} from "./createAndLoadContainerUtils.js";
export type {
LoadSummarizerSummaryResult,
OnDemandSummaryResults,
SummaryStage,
} from "./summarizerResultTypes.js";
export {
type ICodeDetailsLoader,
type IFluidModuleWithDetails,
Expand Down
Loading
Loading