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:
co-authored by
Claude Sonnet 4.6
parent
b69f252f37
commit
db281c5175
@@ -4,6 +4,7 @@ node_modules
|
|||||||
# output
|
# output
|
||||||
out
|
out
|
||||||
dist
|
dist
|
||||||
|
build
|
||||||
*.tgz
|
*.tgz
|
||||||
.next/
|
.next/
|
||||||
# code coverage
|
# code coverage
|
||||||
|
|||||||
@@ -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) -------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user