fix(neuron): accept bare {role,content} input on /v1/responses (agent-zero) #80
Reference in New Issue
Block a user
Delete Branch "fix/responses-easy-message-input"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
agent-zero (v2.0+, via litellm) drives the OpenAI Responses API and sends
inputitems in the "easy input message" form — bare{role, content}with notypefield. Our internally-taggedResponsesInputItemrejected them, so axum'sJsonextractor returned 422 and every a0 request failed (both Main→beast and Utility→benjy). Confirmed by on-wire capture of 15 live requests: 36/36 items were bare easy-messages.Round 1 makes
inputparsing match OpenAI's real tolerance (mirrors the top-levelextraescape hatch): newResponsesInputElementaccepts typed items + bare easy-messages (contentoptional fornull) + anOther(Value)catch-all so no unmodeled item can 422 the whole request again;ResponsesContentPartgets a#[serde(other)] Unknownarm;FunctionCallOutput.outputbecomesValue(string or array). Validated against all 15 captured bodies.Tools forwarding + native
function_callprojection on the Responses path is a deliberate follow-up (Round 2), gated on observing a0's post-fix behaviour.🤖 Generated with Claude Code
agent-zero (via litellm) drives the OpenAI Responses API and sends `input` items in the "easy input message" form — bare `{role, content}` objects with NO `type` field. Our `ResponsesInputItem` is internally tagged (`#[serde(tag="type")]`), so every such item failed the untagged `ResponsesInput` deserialize and axum's `Json` extractor returned 422: OpenAIException - Failed to deserialize the JSON body into the target type: data did not match any variant of untagged enum ResponsesInput This was a *total* failure for agent-zero (both the main model on beast and the utility model on benjy), confirmed by on-wire capture of 15 live requests: 36/36 input items were bare easy-messages. Other clients (/v1/chat/completions, /v1/messages) were unaffected — only the Responses path was exercised this strictly, for the first time. Make `input`-item parsing match OpenAI's real tolerance, mirroring the forward-compat `extra: Value` already at the top level of the request: - New `ResponsesInputElement` wraps the existing typed item enum with two more shapes: `EasyMessage { role, content }` (bare, no type; `content` optional so an assistant turn with `content: null` parses) and `Other(Value)` — a catch-all so a single unmodeled item can never again 422 the whole request. The typed enum is unchanged. - `ResponsesContentPart` gains a `#[serde(other)] Unknown` arm (e.g. `refusal`, audio) — dropped in translation, not rejected. - `FunctionCallOutput.output` is now `Value` (string OR array of content parts, per OpenAI) so a structured tool result isn't lost. - Translator handles all three element shapes; easy-messages translate exactly like typed messages, `Other` and unknown parts are dropped. Tests cover the bare-message, null-content, unknown-item, unknown-part, and array-tool-output shapes, validated against the 15 captured bodies. Tools forwarding + native function_call projection on the Responses path is deliberately a follow-up (Round 2), gated on observing how agent-zero consumes responses once unblocked (in-band JSON vs native tool calls). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLyKaJVFDAYnAGiLvVrK8K