Skip to content

Commit 4b68f80

Browse files
committed
fix(site): de-duplicate streamed text deltas from workers-ai and clean up chat endpoint
1 parent 4e76291 commit 4b68f80

3 files changed

Lines changed: 253 additions & 145 deletions

File tree

‎apps/site/src/pages/api/chat.ts‎

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -259,21 +259,9 @@ export const POST: APIRoute = async ({ request }) => {
259259
if (allText) {
260260
await writer.write(encoder.encode(allText));
261261
} else {
262-
const stepTrace = steps.map((s, idx) => ({
263-
step: idx + 1,
264-
finishReason: s.finishReason,
265-
toolCalls: s.toolCalls?.map((tc) => ({
266-
name: tc.toolName,
267-
args: (tc as any).args ?? (tc as any).input,
268-
})),
269-
toolResults: s.toolResults?.map((tr) => ({
270-
name: tr.toolName,
271-
output: (tr as any).output ?? (tr as any).result,
272-
})),
273-
}));
274262
await writer.write(
275263
encoder.encode(
276-
`I searched the CodingCat.dev Sanity content lake. Diagnostic trace:\n${JSON.stringify(stepTrace, null, 2)}`,
264+
"I searched the CodingCat.dev Sanity content lake for your query, but could not find relevant content.",
277265
),
278266
);
279267
}

‎patches/workers-ai-provider.patch‎

Lines changed: 166 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,37 @@
11
diff --git a/dist/index.mjs b/dist/index.mjs
2-
index 764cbf5325502a9836710b1c218e03d2356cd192..219f7886a71ebe7e469718e39abb72d30e1de4b9 100644
2+
index 764cbf5325502a9836710b1c218e03d2356cd192..1afdfcb438874e9d415fb0cd9da7e9fc24605ec8 100644
33
--- a/dist/index.mjs
44
+++ b/dist/index.mjs
5-
@@ -728,16 +728,7 @@ function getMappedStream(response, salvageContext) {
6-
});
7-
}
8-
}
5+
@@ -702,42 +702,8 @@ function getMappedStream(response, salvageContext) {
6+
const directFinishReason = chunk.finish_reason;
7+
if (choiceFinishReason != null) finishReason = mapWorkersAIFinishReason(choiceFinishReason);
8+
else if (directFinishReason != null) finishReason = mapWorkersAIFinishReason(directFinishReason);
9+
- const nativeResponse = chunk.response;
10+
- if (nativeResponse != null && nativeResponse !== "") {
11+
- const responseText = String(nativeResponse);
12+
- if (responseText.length > 0) if (bufferContentForSalvage) contentBuffer += responseText;
13+
- else {
14+
- if (reasoningId) {
15+
- controller.enqueue({
16+
- type: "reasoning-end",
17+
- id: reasoningId
18+
- });
19+
- reasoningId = null;
20+
- }
21+
- if (!textId) {
22+
- textId = generateId();
23+
- controller.enqueue({
24+
- type: "text-start",
25+
- id: textId
26+
- });
27+
- }
28+
- controller.enqueue({
29+
- type: "text-delta",
30+
- id: textId,
31+
- delta: responseText
32+
- });
33+
- }
34+
- }
935
- if (Array.isArray(chunk.tool_calls)) {
1036
- if (reasoningId) {
1137
- controller.enqueue({
@@ -16,38 +42,126 @@ index 764cbf5325502a9836710b1c218e03d2356cd192..219f7886a71ebe7e469718e39abb72d3
1642
- }
1743
- emitToolCallDeltas(chunk.tool_calls, controller);
1844
- }
45+
+ let textEmitted = false;
1946
+ let toolCallsEmitted = false;
2047
if (choices?.[0]?.delta) {
2148
const delta = choices[0].delta;
2249
const reasoningDelta = delta.reasoning_content ?? delta.reasoning;
23-
@@ -788,8 +779,19 @@ function getMappedStream(response, salvageContext) {
24-
reasoningId = null;
25-
}
26-
emitToolCallDeltas(deltaToolCalls, controller);
50+
@@ -756,7 +722,48 @@ function getMappedStream(response, salvageContext) {
51+
});
52+
}
53+
const textDelta = delta.content;
54+
- if (textDelta && textDelta.length > 0) if (bufferContentForSalvage) contentBuffer += textDelta;
55+
+ if (textDelta && textDelta.length > 0) {
56+
+ textEmitted = true;
57+
+ if (bufferContentForSalvage) contentBuffer += textDelta;
58+
+ else {
59+
+ if (reasoningId) {
60+
+ controller.enqueue({
61+
+ type: "reasoning-end",
62+
+ id: reasoningId
63+
+ });
64+
+ reasoningId = null;
65+
+ }
66+
+ if (!textId) {
67+
+ textId = generateId();
68+
+ controller.enqueue({
69+
+ type: "text-start",
70+
+ id: textId
71+
+ });
72+
+ }
73+
+ controller.enqueue({
74+
+ type: "text-delta",
75+
+ id: textId,
76+
+ delta: textDelta
77+
+ });
78+
+ }
79+
+ }
80+
+ const deltaToolCalls = delta.tool_calls;
81+
+ if (Array.isArray(deltaToolCalls)) {
82+
+ if (reasoningId) {
83+
+ controller.enqueue({
84+
+ type: "reasoning-end",
85+
+ id: reasoningId
86+
+ });
87+
+ reasoningId = null;
88+
+ }
89+
+ emitToolCallDeltas(deltaToolCalls, controller);
2790
+ toolCallsEmitted = true;
91+
+ }
92+
+ }
93+
+ const nativeResponse = chunk.response;
94+
+ if (!textEmitted && nativeResponse != null && nativeResponse !== "") {
95+
+ const responseText = String(nativeResponse);
96+
+ if (responseText.length > 0) if (bufferContentForSalvage) contentBuffer += responseText;
97+
else {
98+
if (reasoningId) {
99+
controller.enqueue({
100+
@@ -775,20 +782,19 @@ function getMappedStream(response, salvageContext) {
101+
controller.enqueue({
102+
type: "text-delta",
103+
id: textId,
104+
- delta: textDelta
105+
+ delta: responseText
106+
});
28107
}
29-
}
108+
- const deltaToolCalls = delta.tool_calls;
109+
- if (Array.isArray(deltaToolCalls)) {
110+
- if (reasoningId) {
111+
- controller.enqueue({
112+
- type: "reasoning-end",
113+
- id: reasoningId
114+
- });
115+
- reasoningId = null;
116+
- }
117+
- emitToolCallDeltas(deltaToolCalls, controller);
118+
+ }
30119
+ if (!toolCallsEmitted && Array.isArray(chunk.tool_calls)) {
31120
+ if (reasoningId) {
32121
+ controller.enqueue({
33122
+ type: "reasoning-end",
34123
+ id: reasoningId
35124
+ });
36125
+ reasoningId = null;
37-
+ }
126+
}
38127
+ emitToolCallDeltas(chunk.tool_calls, controller);
39-
+ }
128+
}
40129
},
41130
flush(controller) {
42-
for (const [idx] of activeToolCalls) {
43131
diff --git a/src/streaming.ts b/src/streaming.ts
44-
index 114d54f70ad03f9acb7f57886bc03961c2938cd5..7c116056e71f1410d7a7fcbb4579449c991dab2d 100644
132+
index 114d54f70ad03f9acb7f57886bc03961c2938cd5..c17b561e4a8628df5e429061eb47f0f63ce4d091 100644
45133
--- a/src/streaming.ts
46134
+++ b/src/streaming.ts
47-
@@ -189,16 +189,7 @@ export function getMappedStream(
48-
}
135+
@@ -163,42 +163,8 @@ export function getMappedStream(
136+
finishReason = mapWorkersAIFinishReason(directFinishReason);
49137
}
50138

139+
- // --- Native format: top-level `response` field ---
140+
- const nativeResponse = chunk.response;
141+
- if (nativeResponse != null && nativeResponse !== "") {
142+
- const responseText = String(nativeResponse);
143+
- if (responseText.length > 0) {
144+
- if (bufferContentForSalvage) {
145+
- contentBuffer += responseText;
146+
- } else {
147+
- // Close active reasoning block before text starts
148+
- if (reasoningId) {
149+
- controller.enqueue({ type: "reasoning-end", id: reasoningId });
150+
- reasoningId = null;
151+
- }
152+
- if (!textId) {
153+
- textId = generateId();
154+
- controller.enqueue({ type: "text-start", id: textId });
155+
- }
156+
- controller.enqueue({
157+
- type: "text-delta",
158+
- id: textId,
159+
- delta: responseText,
160+
- });
161+
- }
162+
- }
163+
- }
164+
-
51165
- // --- Native format: top-level `tool_calls` ---
52166
- if (Array.isArray(chunk.tool_calls)) {
53167
- // Close active reasoning block before tool calls start
@@ -58,18 +172,53 @@ index 114d54f70ad03f9acb7f57886bc03961c2938cd5..7c116056e71f1410d7a7fcbb4579449c
58172
- emitToolCallDeltas(chunk.tool_calls as Record<string, unknown>[], controller);
59173
- }
60174
-
175+
+ let textEmitted = false;
61176
+ let toolCallsEmitted = false;
62177
// --- OpenAI format: choices[0].delta ---
63178
if (choices?.[0]?.delta) {
64179
const delta = choices[0].delta;
65-
@@ -253,8 +244,19 @@ export function getMappedStream(
180+
@@ -223,6 +189,7 @@ export function getMappedStream(
181+
182+
const textDelta = delta.content as string | undefined;
183+
if (textDelta && textDelta.length > 0) {
184+
+ textEmitted = true;
185+
if (bufferContentForSalvage) {
186+
contentBuffer += textDelta;
187+
} else {
188+
@@ -253,8 +220,45 @@ export function getMappedStream(
66189
reasoningId = null;
67190
}
68191
emitToolCallDeltas(deltaToolCalls, controller);
69192
+ toolCallsEmitted = true;
70193
}
71194
}
72195
+
196+
+ // --- Native format: top-level `response` field ---
197+
+ const nativeResponse = chunk.response;
198+
+ if (!textEmitted && nativeResponse != null && nativeResponse !== "") {
199+
+ const responseText = String(nativeResponse);
200+
+ if (responseText.length > 0) {
201+
+ if (bufferContentForSalvage) {
202+
+ contentBuffer += responseText;
203+
+ } else {
204+
+ // Close active reasoning block before text starts
205+
+ if (reasoningId) {
206+
+ controller.enqueue({ type: "reasoning-end", id: reasoningId });
207+
+ reasoningId = null;
208+
+ }
209+
+ if (!textId) {
210+
+ textId = generateId();
211+
+ controller.enqueue({ type: "text-start", id: textId });
212+
+ }
213+
+ controller.enqueue({
214+
+ type: "text-delta",
215+
+ id: textId,
216+
+ delta: responseText,
217+
+ });
218+
+ }
219+
+ }
220+
+ }
221+
+
73222
+ // --- Native format: top-level `tool_calls` ---
74223
+ if (!toolCallsEmitted && Array.isArray(chunk.tool_calls)) {
75224
+ // Close active reasoning block before tool calls start

0 commit comments

Comments
 (0)