openzoo 0.48.47 → 0.48.49

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 +39 -3
  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}`;
@@ -934,12 +955,27 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
934
955
  if (rewritablePath(req.method, req.url)) {
935
956
  const rw = await maybeRewriteModel(bodyBuf);
936
957
  if (rw) {
937
- log(`model "${rw.from}" is not on the zoo nearest match ${rw.to} (OPENZOO_DEFAULT_MODEL overrides)`);
958
+ // SAY WHETHER THE OVERRIDE IS ACTUALLY SET, and name it.
959
+ //
960
+ // This used to print "(OPENZOO_DEFAULT_MODEL overrides)" on EVERY
961
+ // rewrite whether or not the variable existed — a hint about a knob,
962
+ // phrased as a statement about this request. It cost a real incident:
963
+ // the proxy was restarted from a shell carrying
964
+ // OPENZOO_DEFAULT_MODEL=deepseek/deepseek-v4-pro-0813, so every
965
+ // claude-sonnet-5 ask was served by deepseek, and the log line looked
966
+ // exactly the same as it always had. deepseek matches the reasoning
967
+ // regex, so a 16-token safety classification became a 4,000-token
968
+ // reasoning generation — 11.5s, past the caller's timeout, and Claude
969
+ // Code reported "claude-sonnet-5 is temporarily unavailable".
970
+ const forced = process.env.OPENZOO_DEFAULT_MODEL;
971
+ log(forced
972
+ ? `model "${rw.from}" -> FORCED to ${forced} by OPENZOO_DEFAULT_MODEL (nearest match would have been ${rw.to})`
973
+ : `model "${rw.from}" is not on the zoo — nearest match ${rw.to}`);
938
974
  bodyBuf = rw.body;
939
975
  }
940
976
  try {
941
977
  const parsed = JSON.parse(bodyBuf.toString('utf8'));
942
- wantsStream = parsed?.stream === true;
978
+ wantsStream = parsed?.stream === true || clientWantsStream;
943
979
  // REASONING MODELS SPEND max_tokens ON THINKING FIRST.
944
980
  //
945
981
  // 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.49",
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",