thincoder 0.12.14 → 0.12.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/provider/sse.mjs +56 -17
package/package.json
CHANGED
package/src/provider/sse.mjs
CHANGED
|
@@ -3,6 +3,51 @@
|
|
|
3
3
|
* Extracted from core.mjs. Parses Server-Sent Events for LLM chat responses.
|
|
4
4
|
*/
|
|
5
5
|
export async function readSSE(response, { onToken, onReasoning, rules, signal, firedPatterns: sharedFired }) {
|
|
6
|
+
// Early intercept: non-SSE responses — either error bodies (HTTP >= 400) or
|
|
7
|
+
// valid single-chunk JSON completions (some APIs return JSON despite stream:true).
|
|
8
|
+
const contentType = response.headers.get("content-type") || ""
|
|
9
|
+
if (!contentType.includes("event-stream")) {
|
|
10
|
+
const body = await response.text().catch(() => "")
|
|
11
|
+
if (response.status >= 400) {
|
|
12
|
+
let errorMsg = ""
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(body)
|
|
15
|
+
errorMsg = parsed?.error?.message
|
|
16
|
+
|| parsed?.base_resp?.status_msg
|
|
17
|
+
|| parsed?.detail
|
|
18
|
+
|| parsed?.message
|
|
19
|
+
|| parsed?.msg
|
|
20
|
+
|| (typeof parsed.error === "string" ? parsed.error : "")
|
|
21
|
+
} catch { /* not JSON */ }
|
|
22
|
+
if (!errorMsg) errorMsg = body.slice(0, 500)
|
|
23
|
+
throw new Error(`API error: HTTP ${response.status} — ${errorMsg}`)
|
|
24
|
+
}
|
|
25
|
+
// HTTP < 400 non-SSE: might be a valid single-chunk JSON response (e.g. proxy
|
|
26
|
+
// stripped SSE framing). Try to parse as a chat.completion.chunk.
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(body)
|
|
29
|
+
const choice = parsed.choices?.[0]
|
|
30
|
+
if (choice) {
|
|
31
|
+
const result = { content: "", reasoning: "", toolCalls: [], usage: parsed.usage ?? null, finishReason: null }
|
|
32
|
+
const delta = choice.delta ?? {}
|
|
33
|
+
result.content = delta.content ?? ""
|
|
34
|
+
result.reasoning = delta.reasoning_content ?? ""
|
|
35
|
+
result.finishReason = choice.finish_reason ?? null
|
|
36
|
+
for (const tc of delta.tool_calls ?? []) {
|
|
37
|
+
const slot = (result.toolCalls[tc.index ?? result.toolCalls.length] ??= { id: "", name: "", arguments: "" })
|
|
38
|
+
if (tc.id) slot.id = tc.id
|
|
39
|
+
if (tc.function?.name && !slot.name) slot.name = tc.function.name
|
|
40
|
+
if (tc.function?.arguments) slot.arguments += tc.function.arguments
|
|
41
|
+
}
|
|
42
|
+
if (result.content) onToken?.(result.content)
|
|
43
|
+
if (result.reasoning) onReasoning?.(result.reasoning)
|
|
44
|
+
return result
|
|
45
|
+
}
|
|
46
|
+
} catch { /* not parseable JSON */ }
|
|
47
|
+
// Not an error response but not a valid chunk either — unexpected
|
|
48
|
+
throw new Error(`API error: HTTP ${response.status} — unexpected non-SSE response: ${body.slice(0, 200)}`)
|
|
49
|
+
}
|
|
50
|
+
|
|
6
51
|
const result = { content: "", reasoning: "", toolCalls: [], usage: null, finishReason: null }
|
|
7
52
|
const decoder = new TextDecoder()
|
|
8
53
|
let buffer = ""
|
|
@@ -86,26 +131,20 @@ export async function readSSE(response, { onToken, onReasoning, rules, signal, f
|
|
|
86
131
|
}
|
|
87
132
|
|
|
88
133
|
if (!hasChoices) {
|
|
89
|
-
|
|
134
|
+
// Stream started as SSE but no choices were parsed — unusual. Include status for debugging.
|
|
135
|
+
const raw = buffer.trim()
|
|
90
136
|
let errorMsg = ""
|
|
91
137
|
try {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|| parsed?.msg
|
|
100
|
-
|| (typeof parsed.error === "string" ? parsed.error : "")
|
|
101
|
-
}
|
|
138
|
+
const parsed = raw ? JSON.parse(raw) : null
|
|
139
|
+
errorMsg = parsed?.error?.message
|
|
140
|
+
|| parsed?.base_resp?.status_msg
|
|
141
|
+
|| parsed?.detail
|
|
142
|
+
|| parsed?.message
|
|
143
|
+
|| parsed?.msg
|
|
144
|
+
|| (typeof parsed.error === "string" ? parsed.error : "")
|
|
102
145
|
} catch { /* not JSON */ }
|
|
103
|
-
if (!errorMsg
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
if (errorMsg) {
|
|
107
|
-
throw new Error(`API error: ${errorMsg}`)
|
|
108
|
-
}
|
|
146
|
+
if (!errorMsg) errorMsg = `SSE stream contained no choices (HTTP ${response.status})`
|
|
147
|
+
throw new Error(`API error: ${errorMsg}`)
|
|
109
148
|
}
|
|
110
149
|
|
|
111
150
|
return result
|