Files
openclaw/src/auto-reply/reply.media-note.test.ts
Peter Steinberger a95c059f11 feat(media): thread ordered media facts through the prompt path (#112601)
Media-facts program PR 5 — the planned additive bridge. Structured
facts travel alongside existing prompt text through prompt prelude,
GetReplyOptions, FollowupRun, queue collect/defer/retry, active
steering, embedded/CLI runs, Gateway offloads (offloadedRefs no longer
discarded; AgentContentPhaseResult carries media), Tlon, and late-media
projection — with prompt bytes golden-equal everywhere. Facts follow
the same adoption/idempotency/late-append lifecycle as prompt text.
Intentionally +175 prod LOC per the audit; the facts-first
hydration/prune deletion PR consumes this carrier next.
2026-07-22 01:14:49 -07:00

89 lines
3.2 KiB
TypeScript

/** Tests media-note behavior as it appears through reply prompt assembly. */
import { describe, expect, it } from "vitest";
import { finalizeInboundContext } from "./reply/inbound-context.js";
import { buildReplyPromptEnvelope } from "./reply/prompt-prelude.js";
describe("getReplyFromConfig media note plumbing", () => {
it("includes all MediaPaths in the agent prompt", () => {
const sessionCtx = finalizeInboundContext({
Body: "hello",
BodyForAgent: "hello",
From: "+1001",
To: "+2000",
MediaPaths: ["/tmp/a.png", "/tmp/b.png"],
MediaUrls: ["/tmp/a.png", "/tmp/b.png"],
});
const envelope = buildReplyPromptEnvelope({
ctx: sessionCtx,
sessionCtx,
baseBody: sessionCtx.BodyForAgent,
hasUserBody: true,
inboundUserContext: "",
isBareSessionReset: false,
startupAction: "new",
prefixedBody: sessionCtx.BodyForAgent,
});
const prompt = envelope.prefixedCommandBody;
const mediaNote = [
"[media attached: 2 files]",
"[media attached 1/2: /tmp/a.png (application/octet-stream)]",
"[media attached 2/2: /tmp/b.png (application/octet-stream)]",
].join("\n");
const replyHint =
"To send an image back, use the message tool with structured media fields such as media, mediaUrl, path, or filePath. Keep caption in the text body.";
expect(prompt).toBe(`${mediaNote}\n${replyHint}\nhello`);
expect(envelope.queuedBody).toBe(`${mediaNote}\n${replyHint}\nhello`);
expect(envelope.transcriptCommandBody).toBe(`${mediaNote}\nhello`);
expect(envelope.media?.map(({ path }) => path)).toEqual(["/tmp/a.png", "/tmp/b.png"]);
const idxA = prompt.indexOf("[media attached 1/2: /tmp/a.png");
const idxB = prompt.indexOf("[media attached 2/2: /tmp/b.png");
expect(idxA).toBeGreaterThanOrEqual(0);
expect(idxB).toBeGreaterThanOrEqual(0);
expect(idxA).toBeLessThan(idxB);
expect(prompt).toContain("hello");
});
it("keeps the real image attachment note after image understanding rewrites the body", () => {
const describedBody = [
"[Image]",
"User text:",
"make this widescreen",
"Description:",
"a red barn at sunset",
].join("\n");
const sessionCtx = finalizeInboundContext({
Body: describedBody,
BodyForAgent: describedBody,
From: "+1001",
To: "+2000",
MediaPaths: ["/tmp/media-store/real-image.png"],
MediaUrls: ["https://example.com/real-image.png"],
MediaTypes: ["image/png"],
MediaUnderstanding: [
{
kind: "image.description",
attachmentIndex: 0,
text: "a red barn at sunset",
provider: "openai",
},
],
});
const prompt = buildReplyPromptEnvelope({
ctx: sessionCtx,
sessionCtx,
baseBody: sessionCtx.BodyForAgent,
hasUserBody: true,
inboundUserContext: "",
isBareSessionReset: false,
startupAction: "new",
prefixedBody: sessionCtx.BodyForAgent,
}).prefixedCommandBody;
expect(prompt).toContain(
"[media attached: /tmp/media-store/real-image.png (image/png) | https://example.com/real-image.png]",
);
expect(prompt).toContain(describedBody);
});
});