Files
openmaic/OpenMAIC/lib/document/transforms/pipeline.ts
2026-08-16 14:58:47 +08:00

105 lines
3.8 KiB
TypeScript

import type { DocumentArtifact, DocumentDiagnostic, DocumentTransformRecord } from '../types';
import type {
DocumentTransform,
DocumentTransformContext,
DocumentTransformPipelineOptions,
DocumentTransformResult,
} from './types';
import { cloneDocumentArtifact, documentTextLength, throwIfAborted } from './utils';
function isAbortError(error: unknown): boolean {
return error instanceof Error && error.name === 'AbortError';
}
export async function transformDocument(
artifact: DocumentArtifact,
transforms: readonly DocumentTransform[],
context: DocumentTransformContext,
options: DocumentTransformPipelineOptions = {},
): Promise<DocumentTransformResult> {
const pipelineStartedAt = Date.now();
const inputChars = documentTextLength(artifact);
const inputAssets = artifact.assets.length;
const failurePolicy = options.failurePolicy ?? 'fail-fast';
const pipelineDiagnostics: DocumentDiagnostic[] = [];
let current = cloneDocumentArtifact(artifact);
throwIfAborted(context.signal);
for (const transform of transforms) {
throwIfAborted(context.signal);
const stepStartedAt = new Date();
const inputBlockCount = current.blocks.length;
const inputAssetCount = current.assets.length;
const priorTransforms = current.transforms ?? [];
const priorDiagnostics = current.diagnostics ?? [];
const recordId = `${transform.id}:${priorTransforms.length + 1}`;
try {
const output = await transform.apply(cloneDocumentArtifact(current), context);
throwIfAborted(context.signal);
const diagnostics = output.diagnostics ?? [];
const record: DocumentTransformRecord = {
id: recordId,
transformId: transform.id,
version: transform.version,
status: output.status ?? 'applied',
startedAt: stepStartedAt.toISOString(),
completedAt: new Date().toISOString(),
inputBlockCount,
outputBlockCount: output.artifact.blocks.length,
inputAssetCount,
outputAssetCount: output.artifact.assets.length,
options:
typeof context.options?.[transform.id] === 'object' && context.options[transform.id]
? (context.options[transform.id] as Record<string, unknown>)
: undefined,
diagnostics,
};
current = cloneDocumentArtifact(output.artifact);
current.transforms = [...priorTransforms, record];
current.diagnostics = [...priorDiagnostics, ...diagnostics];
pipelineDiagnostics.push(...diagnostics);
} catch (error) {
if (isAbortError(error)) throw error;
const message = error instanceof Error ? error.message : String(error);
const diagnostic: DocumentDiagnostic = {
severity: 'error',
message: `Document transform "${transform.id}" failed: ${message}`,
metadata: { transformId: transform.id },
};
const failedRecord: DocumentTransformRecord = {
id: recordId,
transformId: transform.id,
version: transform.version,
status: 'failed',
startedAt: stepStartedAt.toISOString(),
completedAt: new Date().toISOString(),
inputBlockCount,
outputBlockCount: current.blocks.length,
inputAssetCount,
outputAssetCount: current.assets.length,
diagnostics: [diagnostic],
};
current.transforms = [...(current.transforms ?? []), failedRecord];
current.diagnostics = [...(current.diagnostics ?? []), diagnostic];
pipelineDiagnostics.push(diagnostic);
if (failurePolicy === 'fail-fast') throw error;
}
}
return {
artifact: current,
diagnostics: pipelineDiagnostics,
metrics: {
inputChars,
outputChars: documentTextLength(current),
inputAssets,
outputAssets: current.assets.length,
durationMs: Date.now() - pipelineStartedAt,
},
};
}