feat(canvas): finalize mixer reconnect swap and related updates

This commit is contained in:
2026-04-11 07:42:42 +02:00
parent f3dcaf89f2
commit 028fce35c2
52 changed files with 3859 additions and 272 deletions

View File

@@ -26,16 +26,23 @@ export type AgentStructuredOutput = {
previewText: string;
sections: AgentOutputSection[];
metadata: Record<string, string | string[]>;
metadataLabels: Record<string, string>;
qualityChecks: string[];
body: string;
};
export type AgentStructuredMetadataEntry = {
key: string;
values: string[];
};
export type AgentStructuredOutputDraft = Partial<
AgentStructuredOutput & {
sections: Array<Partial<AgentOutputSection> | null>;
metadata: Record<string, unknown>;
}
>;
Omit<AgentStructuredOutput, "sections" | "metadata">
> & {
sections?: unknown[];
metadata?: Record<string, unknown>;
metadataEntries?: unknown[];
};
export type AgentExecutionStep = {
id: string;
@@ -178,6 +185,93 @@ function normalizeStructuredMetadata(raw: unknown): Record<string, string | stri
return metadata;
}
function normalizeStructuredMetadataEntries(raw: unknown): Record<string, string | string[]> {
if (!Array.isArray(raw)) {
return {};
}
const metadata: Record<string, string | string[]> = {};
for (const item of raw) {
if (!item || typeof item !== "object" || Array.isArray(item)) {
continue;
}
const record = item as Record<string, unknown>;
const key = trimString(record.key);
if (key === "") {
continue;
}
const values = normalizeStringArray(record.values);
const singleValue = trimString(record.value);
if (values.length > 1) {
metadata[key] = values;
continue;
}
if (values.length === 1) {
metadata[key] = values[0]!;
continue;
}
if (singleValue !== "") {
metadata[key] = singleValue;
}
}
return metadata;
}
function slugifyMetadataKey(value: string): string {
const normalized = value
.replace(/ä/g, "ae")
.replace(/ö/g, "oe")
.replace(/ü/g, "ue")
.replace(/Ä/g, "ae")
.replace(/Ö/g, "oe")
.replace(/Ü/g, "ue")
.replace(/ß/g, "ss")
.normalize("NFKD")
.replace(/[\u0300-\u036f]/g, "")
.toLowerCase()
.replace(/[^\x20-\x7e]+/g, " ")
.replace(/[^a-z0-9]+/g, "_")
.replace(/^_+|_+$/g, "");
return normalized || "metadata";
}
function sanitizeStructuredMetadata(raw: Record<string, string | string[]>): {
metadata: Record<string, string | string[]>;
metadataLabels: Record<string, string>;
} {
const metadata: Record<string, string | string[]> = {};
const metadataLabels: Record<string, string> = {};
for (const [rawKey, value] of Object.entries(raw)) {
const trimmedKey = trimString(rawKey);
if (trimmedKey === "") {
continue;
}
const slugBase = slugifyMetadataKey(trimmedKey);
let slug = slugBase;
let suffix = 2;
while (slug in metadata) {
slug = `${slugBase}_${suffix}`;
suffix += 1;
}
metadata[slug] = value;
metadataLabels[slug] = trimmedKey;
}
return { metadata, metadataLabels };
}
function derivePreviewTextFromSections(sections: AgentOutputSection[]): string {
return sections[0]?.content ?? "";
}
@@ -365,7 +459,12 @@ export function normalizeAgentStructuredOutput(
trimString(draft.artifactType) || trimString(fallback.artifactType) || SAFE_FALLBACK_OUTPUT_TYPE;
const sections = normalizeOutputSections(draft.sections);
const previewText = trimString(draft.previewText) || derivePreviewTextFromSections(sections);
const metadata = normalizeStructuredMetadata(draft.metadata);
const metadataFromEntries = normalizeStructuredMetadataEntries(draft.metadataEntries);
const rawMetadata =
Object.keys(metadataFromEntries).length > 0
? metadataFromEntries
: normalizeStructuredMetadata(draft.metadata);
const { metadata, metadataLabels } = sanitizeStructuredMetadata(rawMetadata);
const qualityChecks = normalizeStringArray(draft.qualityChecks);
const body =
trimString(draft.body) ||
@@ -382,6 +481,7 @@ export function normalizeAgentStructuredOutput(
previewText,
sections,
metadata,
metadataLabels,
qualityChecks,
body,
};