server: align ai.chat handler param shape with its OpenRPC spec #140

Open
opened 2026-05-14 10:04:22 +00:00 by timur · 1 comment
Owner

Problem

The chat.openrpc.json spec declares ai.chat with a single named param:

"params": [
  { "name": "request", "required": true, "schema": { "$ref": "#/components/schemas/ChatRequest" } }
]

OpenRPC named-params convention puts the value at params.request. The openrpc_client! macro correctly generates AiChatInput { request: ChatRequest } which serialises to {"jsonrpc":"2.0","method":"ai.chat","params":{"request":{...}}}.

But the server's handler (crates/hero_aibroker_server/src/api_openrpc/chat/handlers.rs::ai_chat) reads params flat:

if !params_value.get("model").map(Value::is_string).unwrap_or(false) { ... }
let mut request: ChatRequest = serde_json::from_value(params_value)?;

So macro-generated clients fail with 'model' parameter required because params_value is {"request":{...}}, not the ChatRequest itself.

#131 worked around this by hand-rolling chat::Client::ai_chat to send ChatRequest directly (flat). That fixes the client but locks the chat domain out of macro-generated code generation, and any consumer that builds a client from the spec themselves will hit the same bug.

Proposal

Pick one of two:

Option A — unwrap params["request"] on the server (smallest)

In chat/handlers.rs::ai_chat and the other chat methods (ai.messages, ai.responses, ai.completions), at the top:

let mut params_value = params.clone();
// Accept both spec-shape ({"request": {...}}) and legacy flat shape.
if let Some(req) = params_value.get("request").cloned() {
    params_value = req;
}

Two-line change per method. The hand-rolled chat::Client::ai_chat in the SDK can then be removed and replaced with the macro-generated one.

Option B — flatten the spec

Change chat.openrpc.json ai.chat params from [{"name": "request", "schema": ChatRequest}] to an enumeration of each ChatRequest field (model, messages, temperature, …). Loses $ref reuse and bloats the spec to ~30 named params per method. Not recommended.

Recommendation

A. Server unwraps; spec stays clean; macro-generated clients work.

Acceptance

  • crates/hero_aibroker_server/src/api_openrpc/chat/handlers.rs accepts both wrapped ({"request":{...}}) and legacy flat params for ai.chat, ai.messages, ai.responses, ai.completions
  • Hand-rolled chat::Client::ai_chat in crates/hero_aibroker_sdk/src/lib.rs deleted; replaced with the macro-generated ai_chat(AiChatInput)
  • crates/hero_aibroker_sdk/src/prompt.rs::PromptBuilder uses macro-generated AiChatInput { request: ChatRequest } shape
  • Live RPC: cargo run -p hero_aibroker_examples --example verify_127_chat still returns a real response (regression check)

Refs #127, #131.

## Problem The `chat.openrpc.json` spec declares `ai.chat` with a single named param: ```jsonc "params": [ { "name": "request", "required": true, "schema": { "$ref": "#/components/schemas/ChatRequest" } } ] ``` OpenRPC named-params convention puts the value at `params.request`. The `openrpc_client!` macro correctly generates `AiChatInput { request: ChatRequest }` which serialises to `{"jsonrpc":"2.0","method":"ai.chat","params":{"request":{...}}}`. But the server's handler (`crates/hero_aibroker_server/src/api_openrpc/chat/handlers.rs::ai_chat`) reads params **flat**: ```rust if !params_value.get("model").map(Value::is_string).unwrap_or(false) { ... } let mut request: ChatRequest = serde_json::from_value(params_value)?; ``` So macro-generated clients fail with `'model' parameter required` because `params_value` is `{"request":{...}}`, not the ChatRequest itself. [#131](https://forge.ourworld.tf/lhumina_code/hero_aibroker/pulls/131) worked around this by hand-rolling `chat::Client::ai_chat` to send `ChatRequest` directly (flat). That fixes the client but locks the chat domain out of macro-generated code generation, and any consumer that builds a client from the spec themselves will hit the same bug. ## Proposal Pick one of two: ### Option A — unwrap `params["request"]` on the server (smallest) In `chat/handlers.rs::ai_chat` and the other chat methods (`ai.messages`, `ai.responses`, `ai.completions`), at the top: ```rust let mut params_value = params.clone(); // Accept both spec-shape ({"request": {...}}) and legacy flat shape. if let Some(req) = params_value.get("request").cloned() { params_value = req; } ``` Two-line change per method. The hand-rolled `chat::Client::ai_chat` in the SDK can then be removed and replaced with the macro-generated one. ### Option B — flatten the spec Change `chat.openrpc.json` `ai.chat` params from `[{"name": "request", "schema": ChatRequest}]` to an enumeration of each ChatRequest field (`model`, `messages`, `temperature`, …). Loses `$ref` reuse and bloats the spec to ~30 named params per method. Not recommended. ## Recommendation **A**. Server unwraps; spec stays clean; macro-generated clients work. ## Acceptance - [ ] `crates/hero_aibroker_server/src/api_openrpc/chat/handlers.rs` accepts both wrapped (`{"request":{...}}`) and legacy flat params for `ai.chat`, `ai.messages`, `ai.responses`, `ai.completions` - [ ] Hand-rolled `chat::Client::ai_chat` in `crates/hero_aibroker_sdk/src/lib.rs` deleted; replaced with the macro-generated `ai_chat(AiChatInput)` - [ ] `crates/hero_aibroker_sdk/src/prompt.rs::PromptBuilder` uses macro-generated `AiChatInput { request: ChatRequest }` shape - [ ] Live RPC: `cargo run -p hero_aibroker_examples --example verify_127_chat` still returns a real response (regression check) Refs [#127](https://forge.ourworld.tf/lhumina_code/hero_aibroker/issues/127), [#131](https://forge.ourworld.tf/lhumina_code/hero_aibroker/pulls/131).
Author
Owner

Implemented in #141#141.

Live-verified against a rebuilt broker (real Groq round-trip).

Implemented in #141 — https://forge.ourworld.tf/lhumina_code/hero_aibroker/pulls/141. Live-verified against a rebuilt broker (real Groq round-trip).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_aibroker#140
No description provided.