incognitolm commited on
Commit
8d1cf35
·
verified ·
1 Parent(s): baab24d

Update server/chatStream.js

Browse files
Files changed (1) hide show
  1. server/chatStream.js +12 -5
server/chatStream.js CHANGED
@@ -39,7 +39,7 @@ export async function streamChat(ws, {
39
 
40
  const messages = [
41
  { role: "system", content: SYSTEM_PROMPT },
42
- ...history.map(normalizeMessage),
43
  { role: "user", content: userMessage },
44
  ];
45
 
@@ -119,7 +119,7 @@ export async function streamChat(ws, {
119
  // Follow-up response after tool calls
120
  const followUpMessages = [
121
  { role: "system", content: SYSTEM_PROMPT },
122
- ...history.map(normalizeMessage),
123
  { role: "user", content: userMessage },
124
  { role: "assistant", content: assistantText || "", tool_calls: toolCalls },
125
  ...toolResults,
@@ -138,11 +138,12 @@ export async function streamChat(ws, {
138
 
139
  if (followUp.ok) {
140
  const fuReader = followUp.body.getReader();
 
141
  let fuBuffer = "";
142
  while (true) {
143
  const { done, value } = await fuReader.read();
144
  if (done) break;
145
- fuBuffer += decoder.decode(value, { stream: true });
146
  const fuLines = fuBuffer.split("\n");
147
  fuBuffer = fuLines.pop() || "";
148
  for (const line of fuLines) {
@@ -171,11 +172,17 @@ export async function streamChat(ws, {
171
  }
172
  }
173
 
 
 
174
  function normalizeMessage(msg) {
 
 
 
 
175
  if (msg.role === "assistant" && msg.tool_calls) {
176
  return { role: "assistant", content: "", tool_calls: msg.tool_calls };
177
  }
178
- // Flatten multipart content arrays to text-only for history
179
  if (Array.isArray(msg.content)) {
180
  const textOnly = msg.content
181
  .filter(b => b.type === "text")
@@ -386,4 +393,4 @@ async function processToolCalls(ws, toolCalls, tools, accessToken, clientId, abo
386
  }
387
 
388
  return toolResults;
389
- }
 
39
 
40
  const messages = [
41
  { role: "system", content: SYSTEM_PROMPT },
42
+ ...history.map(normalizeMessage).filter(Boolean),
43
  { role: "user", content: userMessage },
44
  ];
45
 
 
119
  // Follow-up response after tool calls
120
  const followUpMessages = [
121
  { role: "system", content: SYSTEM_PROMPT },
122
+ ...history.map(normalizeMessage).filter(Boolean),
123
  { role: "user", content: userMessage },
124
  { role: "assistant", content: assistantText || "", tool_calls: toolCalls },
125
  ...toolResults,
 
138
 
139
  if (followUp.ok) {
140
  const fuReader = followUp.body.getReader();
141
+ const fuDecoder = new TextDecoder(); // fresh decoder — reusing the first one corrupts the stream
142
  let fuBuffer = "";
143
  while (true) {
144
  const { done, value } = await fuReader.read();
145
  if (done) break;
146
+ fuBuffer += fuDecoder.decode(value, { stream: true });
147
  const fuLines = fuBuffer.split("\n");
148
  fuBuffer = fuLines.pop() || "";
149
  for (const line of fuLines) {
 
172
  }
173
  }
174
 
175
+ const VALID_ROLES = new Set(["system", "user", "assistant", "tool"]);
176
+
177
  function normalizeMessage(msg) {
178
+ // Drop asset entries (role: "image"/"video"/"audio") — these are UI-only
179
+ // and sending them to the LLM causes invalid-role rejections / blank responses
180
+ if (!VALID_ROLES.has(msg.role)) return null;
181
+
182
  if (msg.role === "assistant" && msg.tool_calls) {
183
  return { role: "assistant", content: "", tool_calls: msg.tool_calls };
184
  }
185
+ // Flatten multipart content arrays (e.g. image attachments) to text-only for history
186
  if (Array.isArray(msg.content)) {
187
  const textOnly = msg.content
188
  .filter(b => b.type === "text")
 
393
  }
394
 
395
  return toolResults;
396
+ }