openzoo 0.48.67 → 0.48.69

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/anthropic.js +37 -1
  2. package/package.json +1 -1
package/lib/anthropic.js CHANGED
@@ -87,6 +87,28 @@ export function anthropicToOpenAI(body) {
87
87
  delete out.system;
88
88
  delete out.anthropic_version;
89
89
  delete out.metadata;
90
+ // A SERVER-SIDE web_search TOOL IS A PLUGIN, NOT A FUNCTION — TRANSLATE IT.
91
+ //
92
+ // Claude Code's Web Search arrives as an Anthropic server tool (type
93
+ // "web_search_20250305", name "web_search") with NO input_schema, because
94
+ // Anthropic runs it itself. Routed to OpenRouter->some other provider, nobody
95
+ // runs it, and the old code simply DROPPED it (no schema) — so the model got
96
+ // no search capability and every Web Search came back "0 results". Silent,
97
+ // and worse than the 400 it replaced: the agent believes it searched.
98
+ //
99
+ // OpenRouter's `web` plugin is the cross-model equivalent — search-then-inject
100
+ // middleware that works on every model. Detect the server tool and switch it
101
+ // on, mapping max_uses -> max_results, rather than discarding the intent.
102
+ const webTool = (Array.isArray(body.tools) ? body.tools : [])
103
+ .find((t) => t?.type?.startsWith?.('web_search') || t?.name === 'web_search');
104
+ if (webTool) {
105
+ const existing = Array.isArray(body.plugins) ? body.plugins : [];
106
+ if (!existing.some((p) => p?.id === 'web')) {
107
+ const web = { id: 'web' };
108
+ if (Number.isFinite(webTool.max_uses)) web.max_results = webTool.max_uses;
109
+ out.plugins = [...existing, web];
110
+ }
111
+ }
90
112
  if (Array.isArray(body.tools) && body.tools.length) {
91
113
  out.tools = body.tools
92
114
  .filter((t) => t?.name && t?.input_schema)
@@ -96,7 +118,21 @@ export function anthropicToOpenAI(body) {
96
118
  }));
97
119
  if (!out.tools.length) delete out.tools;
98
120
  }
99
- if (body.tool_choice?.type === 'auto') out.tool_choice = 'auto';
121
+ // TOOL_CHOICE MAY NOT OUTLIVE TOOLS.
122
+ //
123
+ // The block above DROPS any tool without a name+input_schema — which is every
124
+ // server-side tool (web_search, computer, bash), because those carry no schema
125
+ // in the Anthropic shape. When that empties the list, `out.tools` is deleted.
126
+ // But tool_choice was set unconditionally, so the body went upstream with a
127
+ // choice and nothing to choose from.
128
+ //
129
+ // OBSERVED on grok-4.6: a Claude Code Web Search 400'd with "A tool_choice was
130
+ // set on the request but no tools were specified." Every provider rejects this
131
+ // shape; it just words it differently. Gate the choice on tools surviving.
132
+ const hasTools = Array.isArray(out.tools) && out.tools.length > 0;
133
+ if (!hasTools) {
134
+ delete out.tool_choice;
135
+ } else if (body.tool_choice?.type === 'auto') out.tool_choice = 'auto';
100
136
  else if (body.tool_choice?.type === 'any') out.tool_choice = 'required';
101
137
  else if (body.tool_choice?.type === 'tool') {
102
138
  out.tool_choice = { type: 'function', function: { name: body.tool_choice.name } };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.48.67",
3
+ "version": "0.48.69",
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",