mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-21 16:41:56 +00:00
fix(media-understanding): guard malformed attachments arrays
This commit is contained in:
committed by
Peter Steinberger
parent
f7c658efb9
commit
9c9ab891c2
29
src/media-understanding/attachments.guards.test.ts
Normal file
29
src/media-understanding/attachments.guards.test.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { MediaAttachment } from "./types.js";
|
||||
import { selectAttachments } from "./attachments.js";
|
||||
|
||||
describe("media-understanding selectAttachments guards", () => {
|
||||
it("does not throw when attachments is undefined", () => {
|
||||
const run = () =>
|
||||
selectAttachments({
|
||||
capability: "image",
|
||||
attachments: undefined as unknown as MediaAttachment[],
|
||||
policy: { prefer: "path" },
|
||||
});
|
||||
|
||||
expect(run).not.toThrow();
|
||||
expect(run()).toEqual([]);
|
||||
});
|
||||
|
||||
it("does not throw when attachments is not an array", () => {
|
||||
const run = () =>
|
||||
selectAttachments({
|
||||
capability: "audio",
|
||||
attachments: { malformed: true } as unknown as MediaAttachment[],
|
||||
policy: { prefer: "url" },
|
||||
});
|
||||
|
||||
expect(run).not.toThrow();
|
||||
expect(run()).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -169,23 +169,24 @@ function orderAttachments(
|
||||
attachments: MediaAttachment[],
|
||||
prefer?: MediaUnderstandingAttachmentsConfig["prefer"],
|
||||
): MediaAttachment[] {
|
||||
const list = Array.isArray(attachments) ? attachments : [];
|
||||
if (!prefer || prefer === "first") {
|
||||
return attachments;
|
||||
return list;
|
||||
}
|
||||
if (prefer === "last") {
|
||||
return [...attachments].toReversed();
|
||||
return [...list].toReversed();
|
||||
}
|
||||
if (prefer === "path") {
|
||||
const withPath = attachments.filter((item) => item.path);
|
||||
const withoutPath = attachments.filter((item) => !item.path);
|
||||
const withPath = list.filter((item) => item.path);
|
||||
const withoutPath = list.filter((item) => !item.path);
|
||||
return [...withPath, ...withoutPath];
|
||||
}
|
||||
if (prefer === "url") {
|
||||
const withUrl = attachments.filter((item) => item.url);
|
||||
const withoutUrl = attachments.filter((item) => !item.url);
|
||||
const withUrl = list.filter((item) => item.url);
|
||||
const withoutUrl = list.filter((item) => !item.url);
|
||||
return [...withUrl, ...withoutUrl];
|
||||
}
|
||||
return attachments;
|
||||
return list;
|
||||
}
|
||||
|
||||
export function selectAttachments(params: {
|
||||
@@ -194,7 +195,8 @@ export function selectAttachments(params: {
|
||||
policy?: MediaUnderstandingAttachmentsConfig;
|
||||
}): MediaAttachment[] {
|
||||
const { capability, attachments, policy } = params;
|
||||
const matches = attachments.filter((item) => {
|
||||
const input = Array.isArray(attachments) ? attachments : [];
|
||||
const matches = input.filter((item) => {
|
||||
// Skip already-transcribed audio attachments from preflight
|
||||
if (capability === "audio" && item.alreadyTranscribed) {
|
||||
return false;
|
||||
|
||||
29
src/media-understanding/runner.entries.guards.test.ts
Normal file
29
src/media-understanding/runner.entries.guards.test.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { MediaUnderstandingDecision } from "./types.js";
|
||||
import { formatDecisionSummary } from "./runner.entries.js";
|
||||
|
||||
describe("media-understanding formatDecisionSummary guards", () => {
|
||||
it("does not throw when decision.attachments is undefined", () => {
|
||||
const run = () =>
|
||||
formatDecisionSummary({
|
||||
capability: "image",
|
||||
outcome: "skipped",
|
||||
attachments: undefined as unknown as MediaUnderstandingDecision["attachments"],
|
||||
});
|
||||
|
||||
expect(run).not.toThrow();
|
||||
expect(run()).toBe("image: skipped");
|
||||
});
|
||||
|
||||
it("does not throw when attachment attempts is malformed", () => {
|
||||
const run = () =>
|
||||
formatDecisionSummary({
|
||||
capability: "video",
|
||||
outcome: "skipped",
|
||||
attachments: [{ attachmentIndex: 0, attempts: { bad: true } }],
|
||||
} as unknown as MediaUnderstandingDecision);
|
||||
|
||||
expect(run).not.toThrow();
|
||||
expect(run()).toBe("video: skipped (0/1)");
|
||||
});
|
||||
});
|
||||
@@ -345,16 +345,18 @@ async function resolveProviderExecutionContext(params: {
|
||||
}
|
||||
|
||||
export function formatDecisionSummary(decision: MediaUnderstandingDecision): string {
|
||||
const total = decision.attachments.length;
|
||||
const success = decision.attachments.filter(
|
||||
(entry) => entry.chosen?.outcome === "success",
|
||||
).length;
|
||||
const chosen = decision.attachments.find((entry) => entry.chosen)?.chosen;
|
||||
const attachments = Array.isArray(decision.attachments) ? decision.attachments : [];
|
||||
const total = attachments.length;
|
||||
const success = attachments.filter((entry) => entry?.chosen?.outcome === "success").length;
|
||||
const chosen = attachments.find((entry) => entry?.chosen)?.chosen;
|
||||
const provider = chosen?.provider?.trim();
|
||||
const model = chosen?.model?.trim();
|
||||
const modelLabel = provider ? (model ? `${provider}/${model}` : provider) : undefined;
|
||||
const reason = decision.attachments
|
||||
.flatMap((entry) => entry.attempts.map((attempt) => attempt.reason).filter(Boolean))
|
||||
const reason = attachments
|
||||
.flatMap((entry) => {
|
||||
const attempts = Array.isArray(entry?.attempts) ? entry.attempts : [];
|
||||
return attempts.map((attempt) => attempt?.reason).filter(Boolean);
|
||||
})
|
||||
.find(Boolean);
|
||||
const shortReason = reason ? reason.split(":")[0]?.trim() : undefined;
|
||||
const countLabel = total > 0 ? ` (${success}/${total})` : "";
|
||||
|
||||
Reference in New Issue
Block a user