openzoo 0.48.47 → 0.48.48

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.
Files changed (2) hide show
  1. package/lib/proxy.js +23 -2
  2. package/package.json +1 -1
package/lib/proxy.js CHANGED
@@ -863,6 +863,10 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
863
863
  // answer back on the way out. See lib/anthropic.js.
864
864
  let anthropicMode = false;
865
865
  let anthropicModel = null;
866
+ // The CLIENT's streaming intent, kept separate from the body we send
867
+ // upstream. The Anthropic lane asks the gateway for a complete message (we
868
+ // can only translate a finished one) while still owing the caller SSE.
869
+ let clientWantsStream = false;
866
870
  let responsesMode = false;
867
871
  let responsesModel = null;
868
872
  let responsesCustom = null; // names of freeform tools needing custom_tool_call on the way back
@@ -912,7 +916,24 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
912
916
  try {
913
917
  const inbound = JSON.parse(bodyBuf.toString('utf8'));
914
918
  anthropicModel = inbound.model;
915
- bodyBuf = Buffer.from(JSON.stringify(anthropicToOpenAI(inbound)));
919
+ // ANTHROPIC CLIENTS CANNOT READ AN OPENAI STREAM.
920
+ //
921
+ // The gateway now streams for real, and `relay()` pipes those frames
922
+ // through untouched — which is correct for an OpenAI client and
923
+ // unreadable to Claude Code, which speaks the Anthropic SSE grammar
924
+ // (message_start / content_block_delta / message_stop). It surfaced as
925
+ // "API returned an empty or malformed response (HTTP 200)": a 200 whose
926
+ // body the client cannot parse.
927
+ //
928
+ // The translation we have (openAIToAnthropic + writeAnthropicSse) works
929
+ // on a COMPLETE message, so this lane asks the gateway not to stream and
930
+ // keeps the buffered translation. That costs Claude Code the
931
+ // time-to-first-byte win until an incremental OpenAI->Anthropic frame
932
+ // translator exists; a readable answer late beats an unreadable one now.
933
+ const converted = anthropicToOpenAI(inbound);
934
+ clientWantsStream = converted.stream === true || inbound.stream === true;
935
+ converted.stream = false;
936
+ bodyBuf = Buffer.from(JSON.stringify(converted));
916
937
  anthropicMode = true;
917
938
  req.url = '/v1/chat/completions';
918
939
  url = `${config.apiBase}${req.url}`;
@@ -939,7 +960,7 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
939
960
  }
940
961
  try {
941
962
  const parsed = JSON.parse(bodyBuf.toString('utf8'));
942
- wantsStream = parsed?.stream === true;
963
+ wantsStream = parsed?.stream === true || clientWantsStream;
943
964
  // REASONING MODELS SPEND max_tokens ON THINKING FIRST.
944
965
  //
945
966
  // The budget covers hidden reasoning AND the visible answer, so a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.48.47",
3
+ "version": "0.48.48",
4
4
  "description": "Local x402-paying proxy + MCP server for openzoo.fun — point any OpenAI-compatible harness (Cursor, Claude Code, aider, SDKs) at localhost and it pays per call from a local burner wallet. Solana and Base rails live; Robinhood experimental.",
5
5
  "license": "MIT",
6
6
  "type": "module",