fix: track message attachment aliases

This commit is contained in:
Peter Steinberger
2026-05-15 09:44:16 +01:00
parent 24e88bcdd1
commit 3fd4b02eb5
4 changed files with 124 additions and 5 deletions

View File

@@ -320,6 +320,45 @@ describe("createCodexDynamicToolBridge", () => {
]);
});
it("records message tool media attachment aliases as delivery evidence", async () => {
const toolResult = {
content: [{ type: "text", text: "Sent." }],
details: { messageId: "message-1" },
} satisfies AgentToolResult<unknown>;
const tool = createTool({
name: "message",
execute: vi.fn(async () => toolResult),
});
const bridge = createCodexDynamicToolBridge({
tools: [tool],
signal: new AbortController().signal,
});
const result = await handleMessageToolCall(bridge, {
action: "send",
text: "song attached",
media: "/tmp/generated-song.mp3",
attachments: [{ filePath: "/tmp/generated-cover.png" }],
});
expect(result).toEqual(expectInputText("Sent."));
expect(bridge.telemetry.didSendViaMessagingTool).toBe(true);
expect(bridge.telemetry.messagingToolSentMediaUrls).toEqual([
"/tmp/generated-song.mp3",
"/tmp/generated-cover.png",
]);
expect(bridge.telemetry.messagingToolSentTargets).toEqual([
{
tool: "message",
provider: "message",
to: undefined,
threadId: undefined,
text: "song attached",
mediaUrls: ["/tmp/generated-song.mp3", "/tmp/generated-cover.png"],
},
]);
});
it("records internal UI source replies separately from outbound messaging evidence", async () => {
const toolResult = textToolResult("Sent to current chat.", {
status: "ok",

View File

@@ -417,11 +417,32 @@ function readFirstString(record: Record<string, unknown>, keys: string[]): strin
function collectMediaUrls(record: Record<string, unknown>): string[] {
const urls: string[] = [];
for (const key of ["mediaUrl", "media_url", "imageUrl", "image_url"]) {
const value = record[key];
const pushMediaUrl = (value: unknown) => {
if (typeof value === "string" && value.trim()) {
urls.push(value.trim());
}
};
const pushAttachment = (value: unknown) => {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return;
}
const attachment = value as Record<string, unknown>;
for (const key of ["media", "mediaUrl", "path", "filePath", "fileUrl"]) {
pushMediaUrl(attachment[key]);
}
};
for (const key of [
"media",
"mediaUrl",
"media_url",
"path",
"filePath",
"fileUrl",
"imageUrl",
"image_url",
]) {
const value = record[key];
pushMediaUrl(value);
}
for (const key of ["mediaUrls", "media_urls", "imageUrls", "image_urls"]) {
const value = record[key];
@@ -429,9 +450,13 @@ function collectMediaUrls(record: Record<string, unknown>): string[] {
continue;
}
for (const entry of value) {
if (typeof entry === "string" && entry.trim()) {
urls.push(entry.trim());
}
pushMediaUrl(entry);
}
}
const attachments = record.attachments;
if (Array.isArray(attachments)) {
for (const attachment of attachments) {
pushAttachment(attachment);
}
}
return urls;

View File

@@ -1187,6 +1187,43 @@ describe("messaging tool media URL tracking", () => {
expect(ctx.state.pendingMessagingMediaUrls.has("tool-upload-file")).toBe(false);
});
it("commits message attachment aliases as delivery evidence", async () => {
const { ctx } = createTestContext();
const startEvt: ToolExecutionStartEvent = {
type: "tool_execution_start",
toolName: "message",
toolCallId: "tool-attachment-aliases",
args: {
action: "send",
to: "channel:123",
content: "track ready",
media: "/tmp/generated-song.mp3",
attachments: [{ filePath: "/tmp/generated-cover.png" }],
},
};
await handleToolExecutionStart(ctx, startEvt);
const endEvt: ToolExecutionEndEvent = {
type: "tool_execution_end",
toolName: "message",
toolCallId: "tool-attachment-aliases",
isError: false,
result: { ok: true },
};
await handleToolExecutionEnd(ctx, endEvt);
expect(ctx.state.messagingToolSentMediaUrls).toEqual([
"/tmp/generated-song.mp3",
"/tmp/generated-cover.png",
]);
expectRecordFields(requireSingleMessagingTarget(ctx), "messaging target", {
to: "channel:123",
text: "track ready",
mediaUrls: ["/tmp/generated-song.mp3", "/tmp/generated-cover.png"],
});
});
it("commits sendAttachment args as message delivery evidence", async () => {
const { ctx } = createTestContext();

View File

@@ -341,11 +341,23 @@ function pushUniqueMediaUrl(urls: string[], seen: Set<string>, value: unknown):
function collectMessagingMediaUrlsFromRecord(record: Record<string, unknown>): string[] {
const urls: string[] = [];
const seen = new Set<string>();
const pushAttachment = (value: unknown) => {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return;
}
const attachment = value as Record<string, unknown>;
pushUniqueMediaUrl(urls, seen, attachment.media);
pushUniqueMediaUrl(urls, seen, attachment.mediaUrl);
pushUniqueMediaUrl(urls, seen, attachment.path);
pushUniqueMediaUrl(urls, seen, attachment.filePath);
pushUniqueMediaUrl(urls, seen, attachment.fileUrl);
};
pushUniqueMediaUrl(urls, seen, record.media);
pushUniqueMediaUrl(urls, seen, record.mediaUrl);
pushUniqueMediaUrl(urls, seen, record.path);
pushUniqueMediaUrl(urls, seen, record.filePath);
pushUniqueMediaUrl(urls, seen, record.fileUrl);
const mediaUrls = record.mediaUrls;
if (Array.isArray(mediaUrls)) {
@@ -353,6 +365,12 @@ function collectMessagingMediaUrlsFromRecord(record: Record<string, unknown>): s
pushUniqueMediaUrl(urls, seen, mediaUrl);
}
}
const attachments = record.attachments;
if (Array.isArray(attachments)) {
for (const attachment of attachments) {
pushAttachment(attachment);
}
}
return urls;
}