fix: unwrap double-nested opencode.ai responses in proxy

opencode.ai returns the real OpenAI-compatible response as a JSON string
inside choices[0].message.content instead of an object. Added an
adaptResponse handler that detects and unwraps this double-nesting,
merging inner usage/token counts and reasoning_content into the
standard envelope.

Also added build/ to .gitignore.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-06-24 22:19:12 +07:00
co-authored by Claude Sonnet 4.6
parent b69f252f37
commit db281c5175
2 changed files with 43 additions and 0 deletions
+1
View File
@@ -4,6 +4,7 @@ node_modules
# output # output
out out
dist dist
build
*.tgz *.tgz
.next/ .next/
# code coverage # code coverage
+42
View File
@@ -99,6 +99,48 @@ export const MODEL_ROUTES: Record<string, BackendConfig> = {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
adaptResponse: (raw: any) => {
// opencode.ai wraps the real response as a JSON string inside choices[0].message.content.
// Detect this double-nesting and unwrap it so downstream Anthropic/OpenAI adapters
// see the real content and usage instead of a raw stringified blob.
const outerChoice = raw.choices?.[0];
const innerContent = outerChoice?.message?.content;
if (typeof innerContent === "string" && innerContent.startsWith("{")) {
try {
const inner = JSON.parse(innerContent);
const innerChoice = inner.choices?.[0];
if (innerChoice) {
// Merge: use the inner response as the source of truth for content and usage
const innerUsage = inner.usage ?? {};
return {
...raw,
choices: [
{
...outerChoice,
message: {
role: innerChoice.message?.role ?? "assistant",
// Prefer reasoning_content (thinking) when present; fall back to content
content: innerChoice.message?.reasoning_content
? `<thinking>${innerChoice.message.reasoning_content}</thinking>${innerChoice.message.content ?? ""}`
: (innerChoice.message?.content ?? innerContent),
},
finish_reason: innerChoice.finish_reason ?? outerChoice.finish_reason,
},
],
// Prefer inner usage (has real token counts) over outer (often zeros)
usage: {
prompt_tokens: innerUsage.prompt_tokens ?? innerUsage.input_tokens ?? raw.usage?.prompt_tokens ?? 0,
completion_tokens: innerUsage.completion_tokens ?? innerUsage.output_tokens ?? raw.usage?.completion_tokens ?? 0,
total_tokens: innerUsage.total_tokens ?? raw.usage?.total_tokens ?? 0,
},
};
}
} catch {
// Not valid JSON — treat innerContent as plain text
}
}
return raw;
},
}, },
// -- surfsense.com (custom format) ------------------------------------------- // -- surfsense.com (custom format) -------------------------------------------