Before You Submit
I searched existing issues. Related, but different: #2269 (how the transcript is framed in structured mode) and #2268 (empty model output is not treated as a failure). This one is about the request itself: Handy asks OpenRouter for a strict JSON schema, but does not ask OpenRouter to route only to providers that actually support it.
Bug Description
For the OpenRouter provider, Handy sends response_format: {type: "json_schema", strict: true, ...} (llm_client.rs L382-L387), and the code then assumes the answer is a bare JSON object. But the request has no provider field, so OpenRouter's default routing applies, and provider.require_parameters defaults to false. From the OpenRouter docs (Provider Routing):
With the default routing strategy, providers that don't support all the LLM parameters specified in your request can still receive the request, but will ignore unknown parameters.
So for models served by several providers, some requests are routed to an endpoint that ignores response_format and the model answers as it likes. In my case the answer was the JSON object wrapped in a Markdown code fence. That does not parse (serde_json fails at line 1 column 1), and the existing fallback ("Returning raw content") pastes the raw text, fence and all, into the focused app.
Evidence
Model google/gemma-4-26b-a4b-it, endpoints as listed by GET /api/v1/models/google/gemma-4-26b-a4b-it/endpoints on 2026-10-12: 13 providers, 8 list structured_outputs, 5 do not (DekaLLM, Io Net, Makora, Cloudflare, Novita).
One pasted answer, which looked like this:
```json
{"transcription": "..."}
```Log line from that dictation:
[ERROR] Failed to parse structured output JSON: expected value at line 1 column 1. Returning raw content.It happened once in 44 post-processing requests that session. After excluding those five providers in the account's privacy settings (Ignored Providers), 12 out of 12 identical requests were parseable JSON, served by Darkbloom (6), CoreWeave (5) and DeepInfra (1).
Other models are affected in different ways, e.g. for anthropic/claude-haiku-5.5 4 of 10 endpoints do not list structured_outputs (Amazon Bedrock variants), for deepseek/deepseek-v4-flash-0731 7 of 27, for openai/gpt-oss-120b 6 of 24. Some models have all endpoints supporting it (e.g. the Gemini 2.5 models).
Expected Behavior
When Handy asks for a JSON schema, OpenRouter should route the request only to providers that support it, so the response is always the schema-conforming object Handy expects.
Proposed fix
For provider.id == "openrouter", add this to the request body whenever response_format is set:
"provider": { "require_parameters": true }Notes on behaviour:
- If no endpoint can satisfy all parameters, OpenRouter returns an error instead of routing to a provider that ignores them. The existing structured-output failure path (
actions.rsL302, "Falling back to legacy mode") already handles that. require_parametersapplies to all parameters in the request, so it also covers thereasoningobject Handy sends for OpenRouter. If you would rather not narrow the provider pool for that one, the field could be applied only when a schema is requested.- Nothing else changes for users on other providers.
I'm happy to send a PR for exactly this if that sounds right to you.
System Information
App Version: code identical in v0.9.8 and current main for this path (observed on a build of a downstream fork of 0.9.7 with the same llm_client.rs logic; sources re-checked against v0.9.8).
Operating System: Ubuntu 26.04 LTS. Not OS-specific.
Notes
I used an AI assistant (Claude Code) to help investigate this and draft the report; the measurements above come from real requests made with my own OpenRouter key and from the public OpenRouter API.
Activity
0No activity yet.