Summary
With agent.use_gateway_loop=true, running the agentic loop on a non-Anthropic, openai-compatible provider fails on the very first turn with:
[chat(<model>)] schema is not a function. (In 'schema()', 'schema' is an instance of Object)
This kills every agentic subagent run on a non-Anthropic backend — including the dream synthesize writer, so wiki distillation produces 0 pages when the synth model is openai-compatible. The significance judge and all non-agentic chat (gbrain think, query expansion, facts, takes) work fine on the same provider; only the tool-using agentic loop breaks.
Version: 0.41.28.0, AI SDK ai@6.0.174.
Repro
gbrain config set agent.use_gateway_loop true --force
# any openai-compatible recipe model (deepseek/groq/openrouter/etc.)
gbrain agent run --model groq:gpt-5.5 --max-turns 8 "Use the put_page tool once to write a short page, then stop."
gbrain agent logs <job_id>
# -> turn 0: llm_call_failed error="[chat(groq:gpt-5.5)] schema is not a function ..."
Root cause
core/ai/gateway.ts (~line 2360, the chat() tool-construction reduce) builds each tool's inputSchema as a hand-rolled object literal cast to any:
const tools = (opts.tools ?? []).reduce((acc, t) => {
acc[t.name] = {
description: t.description,
inputSchema: { jsonSchema: t.inputSchema } as any, // <-- not a Schema
};
return acc;
}, {} as Record<string, any>);
AI SDK v6 expects inputSchema to be a FlexibleSchema (a Zod schema or the result of jsonSchema(...)), which carries the schema symbol + a validate function. A plain { jsonSchema } object lacks those, so when the SDK normalizes the tool for an openai-compatible provider it invokes the schema as a function and throws. (The Anthropic path happens to tolerate the same object in some configs, which is likely why it went unnoticed.)
Fix
Wrap with the SDK's jsonSchema() helper:
import { /* ... */ jsonSchema } from 'ai';
acc[t.name] = {
description: t.description,
inputSchema: jsonSchema(t.inputSchema),
};
Verified locally: with this one change the full agentic loop runs on an openai-compatible provider (groq/gpt-5.5) — llm_call_completed → tool_called → tool_result → page persisted, embedded, and searchable.
Secondary issue (same loop, parallel tool calls)
After the schema fix, when the model emits more than one tool call in a single turn, only one tool_result is paired back, and the next turn fails with:
[chat(<model>)] Tool result is missing for tool call <id>.
[chat(<model>)] Invalid prompt: The messages do not match the ModelMessage[] schema.
The writes themselves succeed, but the subagent job is then marked dead. The gateway loop should emit a tool_result block for every tool call in the assistant turn (including errors/“not executed” stubs) before the next model call, so the ModelMessage[] history stays valid. This matters because openai-compatible models emit parallel tool calls more readily than Claude.
Impact / who hits this
Anyone pointing gbrain's agentic features at a non-Anthropic provider (cost reasons, local models, gateways). Everything except the agentic loop already works on these providers; this is the last gap for a fully non-Anthropic brain.
Summary
With
agent.use_gateway_loop=true, running the agentic loop on a non-Anthropic, openai-compatible provider fails on the very first turn with:This kills every agentic subagent run on a non-Anthropic backend — including the dream
synthesizewriter, so wiki distillation produces 0 pages when the synth model is openai-compatible. The significance judge and all non-agentic chat (gbrain think, query expansion, facts, takes) work fine on the same provider; only the tool-using agentic loop breaks.Version: 0.41.28.0, AI SDK
ai@6.0.174.Repro
Root cause
core/ai/gateway.ts(~line 2360, thechat()tool-construction reduce) builds each tool'sinputSchemaas a hand-rolled object literal cast toany:AI SDK v6 expects
inputSchemato be aFlexibleSchema(a Zod schema or the result ofjsonSchema(...)), which carries the schema symbol + avalidatefunction. A plain{ jsonSchema }object lacks those, so when the SDK normalizes the tool for an openai-compatible provider it invokes the schema as a function and throws. (The Anthropic path happens to tolerate the same object in some configs, which is likely why it went unnoticed.)Fix
Wrap with the SDK's
jsonSchema()helper:Verified locally: with this one change the full agentic loop runs on an openai-compatible provider (groq/gpt-5.5) —
llm_call_completed → tool_called → tool_result → page persisted, embedded, and searchable.Secondary issue (same loop, parallel tool calls)
After the schema fix, when the model emits more than one tool call in a single turn, only one
tool_resultis paired back, and the next turn fails with:The writes themselves succeed, but the subagent job is then marked
dead. The gateway loop should emit atool_resultblock for every tool call in the assistant turn (including errors/“not executed” stubs) before the next model call, so theModelMessage[]history stays valid. This matters because openai-compatible models emit parallel tool calls more readily than Claude.Impact / who hits this
Anyone pointing gbrain's agentic features at a non-Anthropic provider (cost reasons, local models, gateways). Everything except the agentic loop already works on these providers; this is the last gap for a fully non-Anthropic brain.