Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,47 @@ describe("buildConversationItems", () => {
},
]);
});

it("extracts cloud resource_link attachments into user messages", () => {
const fileUri = "file:///tmp/workspace/attachments/Receipt-2264-0277.pdf";

const events: AcpMessage[] = [
{
type: "acp_message",
ts: 1,
message: {
jsonrpc: "2.0",
id: 1,
method: "session/prompt",
params: {
prompt: [
{ type: "text", text: "what is this about?" },
{
type: "resource_link",
uri: fileUri,
name: "Receipt-2264-0277.pdf",
},
],
},
},
},
];

const result = buildConversationItems(events, null);

expect(result.items).toEqual([
{
type: "user_message",
id: "turn-1-1-user",
content: "what is this about?",
timestamp: 1,
attachments: [
{
id: fileUri,
label: "Receipt-2264-0277.pdf",
},
],
},
]);
});
});
18 changes: 18 additions & 0 deletions apps/code/src/renderer/utils/promptContent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,22 @@ describe("promptContent", () => {
{ id: secondUri, label: "README.md" },
]);
});

it("extracts cloud resource_link attachments from file URIs", () => {
const fileUri = "file:///tmp/workspace/attachments/Receipt-2264-0277.pdf";

const result = extractPromptDisplayContent([
{ type: "text", text: "what is this about?" },
{
type: "resource_link",
uri: fileUri,
name: "Receipt-2264-0277.pdf",
},
]);

expect(result.text).toBe("what is this about?");
expect(result.attachments).toEqual([
{ id: fileUri, label: "Receipt-2264-0277.pdf" },
]);
});
});
49 changes: 41 additions & 8 deletions apps/code/src/renderer/utils/promptContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,46 @@ export function parseAttachmentUri(uri: string): AttachmentRef | null {
return { id: uri, label };
}

function getBlockAttachmentUri(block: ContentBlock): string | null {
function parseFileUri(
uri: string,
fallbackLabel?: string,
): AttachmentRef | null {
if (!uri.startsWith("file://")) {
return null;
}

try {
const pathname = decodeURIComponent(new URL(uri).pathname);
const label =
fallbackLabel?.trim() || getFileName(pathname) || "attachment";
return { id: uri, label };
} catch {
const label = fallbackLabel?.trim() || getFileName(uri) || "attachment";
return { id: uri, label };
}
}

function getBlockAttachmentRef(block: ContentBlock): AttachmentRef | null {
if (block.type === "resource") {
return block.resource.uri ?? null;
const uri = block.resource.uri;
if (!uri) {
return null;
}

return parseAttachmentUri(uri) ?? parseFileUri(uri);
}

if (block.type === "image") {
return block.uri ?? null;
const uri = block.uri;
if (!uri) {
return null;
}

return parseAttachmentUri(uri) ?? parseFileUri(uri);
}

if (block.type === "resource_link") {
return parseAttachmentUri(block.uri) ?? parseFileUri(block.uri, block.name);
}

return null;
Expand Down Expand Up @@ -80,11 +113,11 @@ export function extractPromptDisplayContent(
const seen = new Set<string>();
const attachments: AttachmentRef[] = [];
for (const block of blocks) {
const uri = getBlockAttachmentUri(block);
if (!uri || seen.has(uri)) continue;
const ref = parseAttachmentUri(uri);
if (!ref) continue;
seen.add(uri);
const ref = getBlockAttachmentRef(block);
if (!ref || seen.has(ref.id)) continue;
const { id } = ref;
if (!id) continue;
seen.add(id);
attachments.push(ref);
}

Expand Down
22 changes: 4 additions & 18 deletions apps/code/src/renderer/utils/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,6 @@ function storedEntryToAcpMessage(entry: StoredLogEntry): AcpMessage {
/**
* Create a user message event for display.
*/
export function createUserMessageEvent(text: string, ts: number): AcpMessage {
return {
type: "acp_message",
ts,
message: {
jsonrpc: "2.0",
id: ts,
method: "session/prompt",
params: {
prompt: [{ type: "text", text }],
},
} as JsonRpcRequest,
};
}

/**
* Create a user prompt event from structured content blocks for display.
*/
export function createUserPromptEvent(
prompt: ContentBlock[],
ts: number,
Expand All @@ -70,6 +52,10 @@ export function createUserPromptEvent(
};
}

export function createUserMessageEvent(text: string, ts: number): AcpMessage {
return createUserPromptEvent([{ type: "text", text }], ts);
}

/**
* Create a user shell execute event.
* When id is provided, it's used to track async execution (start/complete).
Expand Down
Loading