Summary
When AI_GATEWAY_ID is set (enabling the /ai/v1/chat/completions path in cloudflare-ai-binding.ts), the Cloudflare provider fails on multi-turn tool conversations with:
```
AiError: Bad input: Error: oneOf at '/' not met, 0 matches:
required properties at '/' are 'prompt',
Type mismatch of '/messages/0/content', 'array' not in 'string',
Type mismatch of '/messages/1/content', 'string' not in 'null',
required properties at '/messages/1' are 'role,content'
```
Single-turn tool requests succeed. The bug only surfaces when messages contains prior function_call + function_call_output history.
Root cause (localized)
cloudflare.ts — formatRequest() message builder (around line 588–623):
When the request includes messages with toolCalls and toolResults, the resulting messages array contains:
- A
user message whose content ends up as an array (not a string) in some paths — CF's /ai/v1/chat/completions requires string content
- An
assistant message with content: null and tool_calls — the endpoint schema may require this to be absent rather than null
The /ai/run/{model} path (used without AI_GATEWAY_ID) does not exhibit this error — its schema appears more permissive.
Impact
- Any multi-turn tool-loop request routed through cloudflare provider via
AI_GATEWAY_ID fails
- Affects
@cf/openai/gpt-oss-120b, @cf/moonshotai/kimi-k2.6, and all other CF tool-capable models
- In llm-gateway: the error is a plain
Error (not a typed LLMProviderError), which additionally blocks fallback (separate gateway-side workaround already applied)
Suggested fix
In cloudflare.ts formatRequest():
- Ensure all
message.content values are coerced to string | null, never string[], before building the CF payload
- For assistant messages with
tool_calls, verify whether the /ai/v1/chat/completions endpoint requires content to be omitted vs null
- Consider throwing
InvalidRequestError (or another typed error) instead of propagating the raw AiError, so callers can distinguish bad-input from transient failures
Reproduction
// Construct an LLMRequest with prior tool loop history:
const request = {
messages: [
{ role: 'user', content: 'list files' },
{ role: 'assistant', content: '', toolCalls: [{ id: 'c1', type: 'function', function: { name: 'bash', arguments: '{"command":"ls"}' } }] },
{ role: 'user', content: 'file1\nfile2', toolResults: [{ id: 'c1', output: 'file1\nfile2' }] },
],
tools: [{ type: 'function', function: { name: 'bash', description: 'run shell', parameters: { type: 'object', properties: { command: { type: 'string' } }, required: ['command'] } } }],
model: '@cf/openai/gpt-oss-120b',
};
// CloudflareProvider.generateResponse(request) → AiError: Bad input
Environment
@stackbilt/llm-providers v1.14.4
AI_GATEWAY_ID set (triggers /ai/v1/chat/completions path in binding)
- Verified:
/ai/run/ endpoint works for same payload, /ai/v1/chat/completions fails
Summary
When
AI_GATEWAY_IDis set (enabling the/ai/v1/chat/completionspath incloudflare-ai-binding.ts), the Cloudflare provider fails on multi-turn tool conversations with:```
AiError: Bad input: Error: oneOf at '/' not met, 0 matches:
required properties at '/' are 'prompt',
Type mismatch of '/messages/0/content', 'array' not in 'string',
Type mismatch of '/messages/1/content', 'string' not in 'null',
required properties at '/messages/1' are 'role,content'
```
Single-turn tool requests succeed. The bug only surfaces when
messagescontains priorfunction_call+function_call_outputhistory.Root cause (localized)
cloudflare.ts—formatRequest()message builder (around line 588–623):When the request includes messages with
toolCallsandtoolResults, the resultingmessagesarray contains:usermessage whosecontentends up as an array (not a string) in some paths — CF's/ai/v1/chat/completionsrequires string contentassistantmessage withcontent: nullandtool_calls— the endpoint schema may require this to be absent rather than nullThe
/ai/run/{model}path (used withoutAI_GATEWAY_ID) does not exhibit this error — its schema appears more permissive.Impact
AI_GATEWAY_IDfails@cf/openai/gpt-oss-120b,@cf/moonshotai/kimi-k2.6, and all other CF tool-capable modelsError(not a typedLLMProviderError), which additionally blocks fallback (separate gateway-side workaround already applied)Suggested fix
In
cloudflare.tsformatRequest():message.contentvalues are coerced tostring | null, neverstring[], before building the CF payloadtool_calls, verify whether the/ai/v1/chat/completionsendpoint requirescontentto be omitted vsnullInvalidRequestError(or another typed error) instead of propagating the rawAiError, so callers can distinguish bad-input from transient failuresReproduction
Environment
@stackbilt/llm-providersv1.14.4AI_GATEWAY_IDset (triggers/ai/v1/chat/completionspath in binding)/ai/run/endpoint works for same payload,/ai/v1/chat/completionsfails