Skip to content

Commit bcd085f

Browse files
AdminAdmin
authored andcommitted
Fix type errors from adding FileMessagePart to MessagePart union
Adding FileMessagePart widened the non-text branch in several consumers that assumed non-text parts are always images. Fix logFormatter, Gemini, console Message, and countTokensAsync to handle file parts, and keep constructMessages narrowing intact when sanitizing file parts (preserve discriminated union).
1 parent 5a47696 commit bcd085f

5 files changed

Lines changed: 41 additions & 25 deletions

File tree

core/llm/countTokens.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ async function countTokensAsync(
102102
if (part.type === "imageUrl") {
103103
return countImageTokens(part);
104104
}
105+
if (part.type === "file") {
106+
return (await encoding.encode(`[Attachment: ${part.name}]`)).length;
107+
}
105108
return (await encoding.encode(part.text ?? "")).length;
106109
});
107110
return (await Promise.all(promises)).reduce((sum, val) => sum + val, 0);

core/llm/llms/Gemini.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,12 @@ class Gemini extends BaseLLM {
210210
};
211211
}
212212

213+
if (part.type === "file") {
214+
return {
215+
text: `[Attachment: ${part.name}]`,
216+
};
217+
}
218+
213219
let data = "";
214220
if (part.imageUrl?.url) {
215221
const extracted = extractBase64FromDataUrl(part.imageUrl.url);

core/llm/logFormatter.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,10 @@ export class LLMLogFormatter {
276276
for (const part of message.content) {
277277
if (part.type === "text") {
278278
this.logMessageText(item, part.text);
279-
} else {
279+
} else if (part.type === "imageUrl") {
280280
this.logLines(item, `Image: ${part.imageUrl.url}`);
281+
} else if (part.type === "file") {
282+
this.logLines(item, `Attachment: ${part.name}`);
281283
}
282284
}
283285
}

gui/src/components/console/Message.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,12 @@ function renderMessageContent(
3333
return message.content.map((part) => {
3434
if (part.type == "text") {
3535
return renderMessageText(part.text);
36-
} else {
36+
} else if (part.type == "imageUrl") {
3737
return <div>Image: {part.imageUrl.url}</div>;
38+
} else if (part.type == "file") {
39+
return <div>Attachment: {part.name}</div>;
3840
}
41+
return null;
3942
});
4043
}
4144
}

gui/src/redux/util/constructMessages.ts

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,25 +34,6 @@ interface MessageWithContextItems {
3434
ctxItems: ContextItemWithId[];
3535
message: ChatMessage;
3636
}
37-
38-
/**
39-
* File attachments are rendered in the UI (via editorState) but are not sent
40-
* to LLM providers, which do not understand the `file` part type. Convert them
41-
* to a text placeholder so every provider receives a safe, valid message.
42-
*/
43-
function sanitizeFilePartsForLLM(content: ChatMessage["content"]): ChatMessage["content"] {
44-
if (!Array.isArray(content)) {
45-
return content;
46-
}
47-
if (!content.some((part) => part.type === "file")) {
48-
return content;
49-
}
50-
return content.map((part) =>
51-
part.type === "file"
52-
? { type: "text" as const, text: `[Attachment: ${part.name}]` }
53-
: part,
54-
);
55-
}
5637
export function constructMessages(
5738
history: ChatHistoryItem[],
5839
baseSystemMessage: string | undefined,
@@ -240,10 +221,31 @@ export function constructMessages(
240221
});
241222
}
242223

243-
const messages = msgs.map((m) => ({
244-
...m.message,
245-
content: sanitizeFilePartsForLLM(m.message.content),
246-
}));
224+
// File attachments are rendered in the UI (via editorState) but are not sent
225+
// to LLM providers, which do not understand the `file` part type. Convert them
226+
// to a text placeholder so every provider receives a safe, valid message.
227+
const messages = msgs.map((m) => {
228+
const { message } = m;
229+
if (
230+
message.role !== "user" &&
231+
message.role !== "assistant" &&
232+
message.role !== "thinking"
233+
) {
234+
return message;
235+
}
236+
if (
237+
typeof message.content === "string" ||
238+
!message.content.some((part) => part.type === "file")
239+
) {
240+
return message;
241+
}
242+
const content = message.content.map((part) =>
243+
part.type === "file"
244+
? { type: "text" as const, text: `[Attachment: ${part.name}]` }
245+
: part,
246+
);
247+
return { ...message, content };
248+
});
247249
return {
248250
messages,
249251
appliedRules,

0 commit comments

Comments
 (0)