Skip to content

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

Description

@Crazyphil

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:
    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:
      {"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 Tool Calls Fail with g4f API in OpenCode #3373, which was fixed only for the Ollama worker (commit 1448f7a). 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: fix(backend): passthrough forwards object-form tool_calls[].function.arguments instead of stringifying diegosouzapw/OmniRoute#14786

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions