Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/supercode-cli/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "supercode-cli",
"version": "0.1.52",
"version": "0.1.53",
"description": "AI-powered coding agent CLI",
"main": "dist/main.js",
"bin": {
Expand Down
43 changes: 43 additions & 0 deletions apps/supercode-cli/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,8 @@ app.post("/api/ai/chat", async (req, res) => {
let buffer = ""
let inputTokens = 0
let outputTokens = 0
let fullContent = ""
let sawToolCalls = false
let pendingToolCalls: Record<number, { id: string; name: string; args: string }> = {}
while (true) {
const { done, value } = await reader.read()
Expand All @@ -573,9 +575,11 @@ app.post("/api/ai/chat", async (req, res) => {
const data = JSON.parse(jsonStr)
const delta = data.choices?.[0]?.delta
if (delta?.content) {
fullContent += delta.content
res.write(JSON.stringify({ type: "text", content: delta.content }) + "\n")
}
if (delta?.tool_calls) {
sawToolCalls = true
for (const tc of delta.tool_calls) {
const index = tc.index ?? 0
if (!pendingToolCalls[index]) {
Expand Down Expand Up @@ -605,6 +609,45 @@ app.post("/api/ai/chat", async (req, res) => {
} catch { /* skip malformed */ }
}
}

// Fallback: if streaming returned no text and no tool calls,
// retry as non-streaming (ConcentrateAI's upstream intermittently
// drops content on streaming requests).
if (!fullContent.trim() && !sawToolCalls) {
const fbBody: any = {
model: modelName,
messages: nonSystemMessages.map((m: any) => {
const msg: any = {
role: m.role,
content: m.content !== null && m.content !== undefined ? String(m.content) : "",
}
if (m.tool_calls) msg.tool_calls = m.tool_calls
if (m.tool_call_id) msg.tool_call_id = m.tool_call_id
return msg
}),
max_tokens: getModelMaxTokens(modelName),
temperature: 0.7,
stream: false,
}
if (system && nonSystemMessages.length > 0) {
fbBody.messages = [{ role: "system", content: system }, ...fbBody.messages]
}
const fbRes = await fetch("https://api.concentrate.ai/v1/chat/completions", {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
body: JSON.stringify(fbBody),
})
Comment on lines +635 to +639

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Fallback fetch lacks a timeout and retry — can hang the request thread indefinitely.

The upstream nonStreamingRequest in concentrate-service.ts uses fetchWithRetry with AbortSignal.timeout(60_000). The fallback fetch here has neither, so a slow or unresponsive ConcentrateAI gateway will hold the Express request thread open with no upper bound.

🔒️ Proposed fix: add timeout to fallback fetch
-          const fbRes = await fetch("https://api.concentrate.ai/v1/chat/completions", {
-            method: "POST",
-            headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
-            body: JSON.stringify(fbBody),
-          })
+          const fbRes = await fetch("https://api.concentrate.ai/v1/chat/completions", {
+            method: "POST",
+            headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
+            body: JSON.stringify(fbBody),
+            signal: AbortSignal.timeout(60_000),
+          })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const fbRes = await fetch("https://api.concentrate.ai/v1/chat/completions", {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
body: JSON.stringify(fbBody),
})
const fbRes = await fetch("https://api.concentrate.ai/v1/chat/completions", {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
body: JSON.stringify(fbBody),
signal: AbortSignal.timeout(60_000),
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/supercode-cli/server/src/index.ts` around lines 635 - 639, The fallback
fetch in the server request flow can hang indefinitely because it bypasses the
timeout/retry behavior used by nonStreamingRequest in concentrate-service.ts.
Update the fallback path around the fbRes fetch call to use the same
fetchWithRetry wrapper and apply an AbortSignal.timeout(60_000) so the Express
request cannot մնain open forever. Keep the fix localized to the fallback
handling in index.ts and preserve the existing fbBody/apiKey request
construction.

if (fbRes.ok) {
const fbData: any = await fbRes.json()
const fbContent = fbData?.choices?.[0]?.message?.content ?? ""
if (fbContent) {
res.write(JSON.stringify({ type: "text", content: fbContent }) + "\n")
}
inputTokens = fbData?.usage?.prompt_tokens ?? 0
outputTokens = fbData?.usage?.completion_tokens ?? 0
}
Comment on lines +640 to +648

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Silent failure and token counter reset on fallback error.

Two issues in this block:

  1. When fbRes.ok is false, the error is silently swallowed — no log, no error event written to the NDJSON stream. The client receives an empty response with no indication of what went wrong.

  2. Lines 646-647 unconditionally set inputTokens/outputTokens with ?? 0. If the fallback response omits usage, any partial token counts from the streaming phase are overwritten with zeros.

🛡️ Proposed fix: handle non-OK and guard token overwrite
           if (fbRes.ok) {
             const fbData: any = await fbRes.json()
             const fbContent = fbData?.choices?.[0]?.message?.content ?? ""
             if (fbContent) {
               res.write(JSON.stringify({ type: "text", content: fbContent }) + "\n")
             }
-            inputTokens = fbData?.usage?.prompt_tokens ?? 0
-            outputTokens = fbData?.usage?.completion_tokens ?? 0
+            if (fbData?.usage) {
+              inputTokens = fbData.usage.prompt_tokens ?? inputTokens
+              outputTokens = fbData.usage.completion_tokens ?? outputTokens
+            }
+          } else {
+            console.error(`ConcentrateAI fallback failed: ${fbRes.status}`)
           }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (fbRes.ok) {
const fbData: any = await fbRes.json()
const fbContent = fbData?.choices?.[0]?.message?.content ?? ""
if (fbContent) {
res.write(JSON.stringify({ type: "text", content: fbContent }) + "\n")
}
inputTokens = fbData?.usage?.prompt_tokens ?? 0
outputTokens = fbData?.usage?.completion_tokens ?? 0
}
if (fbRes.ok) {
const fbData: any = await fbRes.json()
const fbContent = fbData?.choices?.[0]?.message?.content ?? ""
if (fbContent) {
res.write(JSON.stringify({ type: "text", content: fbContent }) + "\n")
}
if (fbData?.usage) {
inputTokens = fbData.usage.prompt_tokens ?? inputTokens
outputTokens = fbData.usage.completion_tokens ?? outputTokens
}
} else {
console.error(`ConcentrateAI fallback failed: ${fbRes.status}`)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/supercode-cli/server/src/index.ts` around lines 640 - 648, The fallback
handling in the streaming response block is silently swallowing failures and
resetting token counts. Update the fbRes.ok branch in index.ts to explicitly
handle the non-OK case by logging the error and writing an error event to the
NDJSON stream, using the existing fallback response flow symbols around fbRes
and res.write. Also change the inputTokens/outputTokens assignment so it only
overwrites the existing counters when fbData.usage is present, preserving any
partial counts from the streaming phase instead of defaulting to zero.

}

recordUsage({
provider: "concentrateai", model: modelName,
inputTokens, outputTokens, cachedInputTokens: 0,
Expand Down
Loading