mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-27 20:14:49 +02:00
[eric] tool-ui: mechanical repair pass for agent payload mistakes (numeric ids, ranked priorities, nested rows), re-validated so it can only flip fail to pass
This commit is contained in:
@@ -46,8 +46,68 @@ type Gate =
|
||||
|
||||
/** Models pad payloads with invented keys; strip ONLY unrecognized-key issues and retry once, so
|
||||
sloppiness self-heals while genuinely wrong shapes still fall back loudly. */
|
||||
function slugFor(label: unknown, i: number): string {
|
||||
const t = typeof label === 'string' ? label.trim().toLowerCase().replace(/\s+/g, '-').slice(0, 40) : '';
|
||||
return t || `item-${i + 1}`;
|
||||
}
|
||||
|
||||
// Mechanical repairs for the mistakes agents actually make (numeric ids, ranked priorities, nested
|
||||
// row objects, bare action objects). Only ever applied when the strict parse FAILED, and the result
|
||||
// is re-validated, so a repair can flip fail->pass but never corrupt a valid payload.
|
||||
function repairCommonAgentShapes(props: Record<string, unknown>): Record<string, unknown> {
|
||||
let out: Record<string, unknown>;
|
||||
try {
|
||||
out = JSON.parse(JSON.stringify(props ?? {}, (_k, v) => (v === undefined ? null : v)));
|
||||
} catch {
|
||||
return props;
|
||||
}
|
||||
const fixIdLabel = (arr: unknown): unknown => {
|
||||
if (!Array.isArray(arr)) return arr;
|
||||
return arr.map((o, i) => {
|
||||
if (typeof o === 'string') return { id: slugFor(o, i), label: o };
|
||||
if (o && typeof o === 'object' && !Array.isArray(o)) {
|
||||
const obj = { ...(o as Record<string, unknown>) };
|
||||
if (obj.id == null || obj.id === '') obj.id = slugFor(obj.label, i);
|
||||
else if (typeof obj.id !== 'string') obj.id = String(obj.id);
|
||||
if (typeof obj.label !== 'string' || !obj.label) obj.label = String(obj.label ?? obj.id);
|
||||
return obj;
|
||||
}
|
||||
return o;
|
||||
});
|
||||
};
|
||||
if ('options' in out) out.options = fixIdLabel(out.options);
|
||||
if ('actions' in out) {
|
||||
if (out.actions && !Array.isArray(out.actions) && typeof out.actions === 'object' && 'label' in (out.actions as object)) out.actions = [out.actions];
|
||||
out.actions = fixIdLabel(out.actions);
|
||||
}
|
||||
const PRIORITY_SYNONYMS: Record<string, string> = { '1': 'primary', '2': 'secondary', '3': 'tertiary', high: 'primary', medium: 'secondary', low: 'tertiary', primary: 'primary', secondary: 'secondary', tertiary: 'tertiary' };
|
||||
if (Array.isArray(out.columns)) {
|
||||
out.columns = out.columns.map((c) => {
|
||||
if (c && typeof c === 'object' && 'priority' in (c as object)) {
|
||||
const mapped = PRIORITY_SYNONYMS[String((c as Record<string, unknown>).priority).toLowerCase()];
|
||||
const copy = { ...(c as Record<string, unknown>) };
|
||||
if (mapped) copy.priority = mapped; else delete copy.priority;
|
||||
return copy;
|
||||
}
|
||||
return c;
|
||||
});
|
||||
}
|
||||
if (Array.isArray(out.data)) {
|
||||
out.data = out.data.map((row) => {
|
||||
if (!row || typeof row !== 'object' || Array.isArray(row)) return row;
|
||||
return Object.fromEntries(Object.entries(row as Record<string, unknown>).map(([k, v]) => {
|
||||
if (v !== null && typeof v === 'object' && !Array.isArray(v)) return [k, JSON.stringify(v)];
|
||||
if (Array.isArray(v)) return [k, v.map((x) => (x !== null && typeof x === 'object' ? JSON.stringify(x) : x))];
|
||||
return [k, v];
|
||||
}));
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function parseLeniently(schema: { safeParse: (v: unknown) => any }, props: Record<string, unknown>): Gate {
|
||||
let result = schema.safeParse(props);
|
||||
let base: Record<string, unknown> = props;
|
||||
if (!result.success) {
|
||||
const issues: Array<{ code: string; keys?: string[]; path: Array<string | number>; message: string }> = result.error.issues;
|
||||
if (issues.every((i) => i.code === 'unrecognized_keys')) {
|
||||
@@ -55,9 +115,14 @@ function parseLeniently(schema: { safeParse: (v: unknown) => any }, props: Recor
|
||||
for (const issue of issues) {
|
||||
for (const key of issue.keys || []) delete cleaned[key];
|
||||
}
|
||||
base = cleaned;
|
||||
result = schema.safeParse(cleaned);
|
||||
}
|
||||
}
|
||||
if (!result.success) {
|
||||
const repaired = schema.safeParse(repairCommonAgentShapes(base));
|
||||
if (repaired.success) return { state: 'ok', parsed: repaired.data as Record<string, unknown> };
|
||||
}
|
||||
if (result.success) return { state: 'ok', parsed: result.data as Record<string, unknown> };
|
||||
const issues = result.error.issues.slice(0, 2).map((i: { path: Array<string | number>; message: string }) => `${i.path.join('.')}: ${i.message}`).join('; ');
|
||||
return { state: 'bad', problem: issues };
|
||||
|
||||
Reference in New Issue
Block a user