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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thincoder",
3
- "version": "0.12.14",
3
+ "version": "0.12.16",
4
4
  "description": "Thin coding agent - zero dependencies, no build step, Node.js native. Sharp code, zero bloat.",
5
5
  "keywords": [
6
6
  "ai",
@@ -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
- const contentType = response.headers.get("content-type") || ""
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 raw = buffer.trim() || ""
93
- if (raw) {
94
- const parsed = JSON.parse(raw)
95
- errorMsg = parsed?.error?.message
96
- || parsed?.base_resp?.status_msg
97
- || parsed?.detail
98
- || parsed?.message
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 && !contentType.includes("event-stream")) {
104
- errorMsg = `Response is not SSE (Content-Type: ${contentType || "unknown"})`
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