{"record":{"id":"a492ccdbe867113d","repo":"Hmbown/CodeWhale","slug":"only-post-v1-chat-completions-is-proxied","errorCode":null,"errorMessage":"only POST /v1/chat/completions is proxied","messagePattern":"only POST /v1/chat/completions is proxied","errorType":"http","errorClass":null,"httpStatus":404,"severity":"error","filePath":"scripts/opencode-chat2responses-proxy.mjs","lineNumber":135,"sourceCode":"        out += `data: ${JSON.stringify({ id: evt.response?.id ?? \"chatcmpl-proxy\", object: \"chat.completion.chunk\", created: Math.floor(Date.now() / 1000), model, choices: [{ index: 0, delta: { tool_calls: [{ index: 0, id: item.call_id, type: \"function\", function: { name: item.name, arguments: \"\" } }] }, finish_reason: null }] })}\\n\\n`;\n      } else if (type === \"response.function_call_arguments.delta\") {\n        out += `data: ${JSON.stringify({ id: evt.response?.id ?? \"chatcmpl-proxy\", object: \"chat.completion.chunk\", created: Math.floor(Date.now() / 1000), model, choices: [{ index: 0, delta: { tool_calls: [{ index: 0, function: { arguments: evt.delta ?? \"\" } }] }, finish_reason: null }] })}\\n\\n`;\n      } else if (type === \"response.completed\" || type === \"response.incomplete\") {\n        out += `data: ${JSON.stringify({ id: evt.response?.id ?? \"chatcmpl-proxy\", object: \"chat.completion.chunk\", created: Math.floor(Date.now() / 1000), model, choices: [{ index: 0, delta: {}, finish_reason: \"stop\" }] })}\\n\\n`;\n      }\n    } catch {}\n  }\n  return out;\n}\n\nconst server = http.createServer(async (req, res) => {\n  if (req.method === \"GET\" && req.url === \"/health\") {\n    res.writeHead(200, { \"content-type\": \"application/json\" });\n    res.end(JSON.stringify({ ok: true, upstream: UPSTREAM_BASE }));\n    return;\n  }\n  if (req.method !== \"POST\" || !req.url?.includes(\"/chat/completions\")) {\n    res.writeHead(404, { \"content-type\": \"application/json\" });\n    res.end(JSON.stringify({ error: \"only POST /v1/chat/completions is proxied\" }));\n    return;\n  }\n  let body = \"\";\n  req.on(\"data\", (chunk) => (body += chunk));\n  req.on(\"end\", async () => {\n    try {\n      const chatBody = JSON.parse(body || \"{}\");\n      const model = chatBody.model ?? \"muse-spark-1.2-contributor-free\";\n      const isStream = chatBody.stream === true;\n      const apiKey = req.headers.authorization?.replace(/^Bearer\\s+/i, \"\") ?? process.env.OPENCODE_ZEN_API_KEY ?? \"\";\n      const responsesBody = chatToResponses(chatBody);\n      const upstreamUrl = `${UPSTREAM_BASE}${UPSTREAM_PATH}`;\n      const headers = {\n        \"content-type\": \"application/json\",\n        accept: isStream ? \"text/event-stream\" : \"application/json\",\n      };\n      if (apiKey) headers.authorization = `Bearer ${apiKey}`;","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/scripts/opencode-chat2responses-proxy.mjs#L117-L153","documentation":"The chat2responses proxy only forwards POST requests whose URL contains /chat/completions (plus GET /health). Anything else gets a 404 JSON error with this message. The proxy intentionally exposes a minimal surface.","triggerScenarios":"Requesting /v1/responses or /models, using GET/PUT/DELETE on a chat-completions path, or hitting any other path with any verb other than the two allowed combos.","commonSituations":"Pointing an OpenAI SDK configured for the Responses API (POST /v1/responses) at this proxy, a client probing /health with POST, or a misconfigured base URL causing requests to /v1/chat/completions/foo with the wrong verb.","solutions":["Use POST with a URL containing /v1/chat/completions","If the client speaks the Responses API, switch it to chat-completions mode or use a different proxy","Use GET /health for health checks","Check the client's base_url so the full path is /v1/chat/completions"],"exampleFix":"// before\nPOST /v1/responses\n// after\nPOST /v1/chat/completions","handlingStrategy":"try-catch","validationCode":"function assertProxyRequest(method, url) {\n  if (method === \"GET\" && url === \"/health\") return;\n  if (method !== \"POST\" || !url.includes(\"/chat/completions\")) {\n    throw new Error(\"proxy only accepts POST /v1/chat/completions or GET /health\");\n  }\n}","typeGuard":null,"tryCatchPattern":"const res = await fetch(proxyUrl, opts);\nif (res.status === 404) {\n  const body = await res.json();\n  if (body.error === \"only POST /v1/chat/completions is proxied\") {\n    throw new Error(\"Configure the client for chat-completions, not responses API\");\n  }\n}","preventionTips":["Set the client's base_url so requests hit /v1/chat/completions","Do not use the Responses API against this proxy","Use GET /health for health checks only","Verify the HTTP verb is POST"],"tags":["proxy","http","routing"],"backgroundTag":"http-error-response","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}