#3527·gpt4free

[Bug] Puter provider relays tool_calls without stringifying function.arguments → AI_TypeValidationError in opencode

Author: CrazyphilCreated Sep 24, 2026Updated Sep 24, 2026
Labelsbug

Before submitting ☑️ I checked Known Issues ☑️ I searched existing issues — found #3373 (same malformed shape, closed after an Ollama-worker-only fix; bug persists via the Puter provider)

Configuration (Required)

  • Model: z-ai:z-ai/glm-5.3 (also reproduced: openrouter:~google/gemini-flash-latest, infron:google/gemini-3.8-flash:priority)
  • Provider: Puter
  • Interface: API (g4f api, OpenAI-compatible /v1/chat/completions)

Bug description Clear steps to reproduce:

  1. Exact command/file used:
    bash
    curl -sN "https://puter.g4f-dev.workers.dev/chat/completions" \
      -H "Authorization: Bearer <key>" -H "Content-Type: application/json" \
      -d '{"model":"z-ai:z-ai/glm-5.3","stream":true,
           "messages":[{"role":"user","content":"Call the bash tool to run: echo hello"}],
           "tools":[{"type":"function","function":{"name":"bash","description":"Run a bash command",
           "parameters":{"type":"object","properties":{"command":{"type":"string"}},"required":["command"]}}}]}'
  2. Environment location: Germany
  3. Observed behavior vs expected:
    • Observed: the streamed chunk delivers delta.tool_calls[0].function.arguments as a JSON object, the tool call has no index field, and the complete tool call plus finish_reason:"stop" arrive in a single chunk:
      json
      {"id":"chatcmpl-1790267014577","object":"chat.completion.chunk","created":1790267014,
       "model":"z-ai:z-ai/glm-5.3","choices":[{"index":0,"delta":{"tool_calls":[
         {"id":"call_c0c7387a74ec4150a6586e15","type":"function",
          "function":{"name":"bash","arguments":{"command":"echo hello"}}}]},
       "finish_reason":"stop"}]}
    • Expected: function.arguments as a JSON-encoded string ("arguments":"{\"command\":\"echo hello\"}"), per the OpenAI streaming schema. Strict clients (opencode via @ai-sdk/openai-compatible) reject the whole stream with AI_TypeValidationError: expected string, received object.

Environment

  • Docker container running OmniRoute
  • Direct connection using ai-sdk to g4f Puter proxy (puter.g4f-dev.workers.dev)

Screenshots/Logs

AI_TypeValidationError: Type validation failed: Value: {"id":"chatcmpl-1790267014577",...,"choices":[{"index":0,"delta":{"tool_calls":[{"id":"call_c0c7387a74ec4150a6586e15","type":"function","function":{"name":"bash","arguments":{"command":"echo hello"}}}]},"finish_reason":"stop"}]}
Error message: [{"code":"invalid_union","errors":[[
  {"expected":"string","code":"invalid_type","path":["choices",0,"delta","tool_calls",0,"function","arguments"],"message":"Invalid input: expected string, received object"}],[
  {"expected":"object","code":"invalid_type","path":["error"],"message":"Invalid input: expected object, received undefined"}]],"path":[],"message":"Invalid input"}]

No error is logged by the g4f API itself — it streams the chunk as if it were valid.

Additional context

  • No proxy/VPN involved. Other providers on the same instance work; the issue is specific to providers that relay native upstream tool calls.
  • Root cause pointer: g4f/Provider/needs_auth/Puter.py (lines ~506–508) relays upstream delta.tool_calls verbatim — yield ToolCalls(choice["delta"]["tool_calls"]) — without normalizing function.arguments to a string (and without adding the streaming index field). g4f's own text-parsed tool path does this correctly via normalize_tool_calls (g4f/tools/tool_support.py, json.dumps(args)), so only the native-relay path leaks the object form.
  • This is the same defect class as #3373, which was fixed only for the Ollama worker (commit 1448f7ad). The Puter relay path still emits the malformed shape as of 2026-09-24.
  • Downstream impact: routing this through a gateway (OmniRoute) with strict-schema clients breaks all tool-calling workflows intermittently, since the malformed chunk is forwarded as-is. Filed downstream bug: diegosouzapw/OmniRoute#14786