{"record":{"id":"40a5fe7106919600","repo":"can1357/oh-my-pi","slug":"field-role-must-be-assistant","errorCode":null,"errorMessage":"{field}.role must be 'assistant'","messagePattern":"(.+?)\\.role must be 'assistant'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/omp-rpc/src/omp_rpc/protocol.py","lineNumber":300,"sourceCode":"    result: list[str] = []\n    for item in values:\n        if not isinstance(item, str):\n            raise ValueError(f\"{field} must contain only strings\")\n        result.append(item)\n    return tuple(result) or None\n\n\ndef _parse_agent_message(payload: JsonObject, *, field: str) -> AgentMessage:\n    _require_literal(\n        payload.get(\"role\"), _AGENT_MESSAGE_ROLE_VALUES, field=f\"{field}.role\"\n    )\n    return cast(AgentMessage, _clone_json_object(payload, field=field))\n\n\ndef _parse_assistant_message(payload: JsonObject, *, field: str) -> AssistantMessage:\n    message = _parse_agent_message(payload, field=field)\n    if message.get(\"role\") != \"assistant\":\n        raise ValueError(f\"{field}.role must be 'assistant'\")\n    return cast(AssistantMessage, message)\n\n\ndef _parse_tool_result_message(payload: JsonObject, *, field: str) -> ToolResultMessage:\n    message = _parse_agent_message(payload, field=field)\n    if message.get(\"role\") != \"toolResult\":\n        raise ValueError(f\"{field}.role must be 'toolResult'\")\n    return cast(ToolResultMessage, message)\n\n\ndef parse_agent_messages(payload: JsonValue | None) -> tuple[AgentMessage, ...]:\n    if payload is None:\n        return ()\n    if not isinstance(payload, list):\n        raise ValueError(\"messages must be a list\")\n\n    messages: list[AgentMessage] = []\n    for index, item in enumerate(payload):","sourceCodeStart":282,"sourceCodeEnd":318,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/omp-rpc/src/omp_rpc/protocol.py#L282-L318","documentation":"The omp-rpc protocol parser validates every agent message's role before casting. This error is raised when a payload passed to _parse_assistant_message (via parse_assistant_message_event, e.g. the 'partial' of an assistantMessageEvent or a 'done' event's message) parses as a valid agent message but its role is not 'assistant'. The library throws it because the typed AssistantMessage contract requires role == 'assistant'; a different role would silently violate the type cast that follows.","triggerScenarios":"Passing a notification whose assistantMessageEvent.partial (or assistantMessageEvent.message for done events) has role 'user' or 'toolResult' instead of 'assistant'; a server bug serializing the wrong message into an assistant event; mixing up message objects when hand-constructing events.","commonSituations":"A custom or older server implementation emits the full transcript message rather than the assistant message in event partials; client code plumbs user messages into assistant-message-event handlers; a protocol version change renamed or reshaped roles.","solutions":["Inspect the notification payload and ensure the partial/message object inside the assistantMessageEvent has role='assistant'","Fix the server/emitter side so assistant events carry assistant-role messages","If consuming raw JSON, route messages with other roles through the appropriate parser (parse_agent_messages) instead of the assistant-specific one","Check for protocol version mismatch between client and server and upgrade the matching omp-rpc package"],"exampleFix":"// before\nparse_assistant_message_event({\"type\": \"start\", \"partial\": {\"role\": \"user\", ...}})\n// after\nparse_assistant_message_event({\"type\": \"start\", \"partial\": {\"role\": \"assistant\", \"content\": [], ...}})","handlingStrategy":"type-guard","validationCode":"def is_assistant_message(m: dict) -> bool:\n    return isinstance(m, dict) and m.get(\"role\") == \"assistant\"\n\npartial = event.get(\"partial\")\nif not is_assistant_message(partial):\n    raise ValueError(\"event partial is not an assistant message\")","typeGuard":"from typing import TypeGuard\n\ndef is_assistant_message(m: object) -> TypeGuard[dict]:\n    return isinstance(m, dict) and m.get(\"role\") == \"assistant\"","tryCatchPattern":"try:\n    event = parse_assistant_message_event(payload)\nexcept ValueError as e:\n    logger.warning(\"malformed assistant event: %s\", e)\n    event = None","preventionTips":["Check the role field of event partials before parsing","Keep client and server protocol versions in sync","Use parse_agent_messages for payloads of unknown role"],"tags":["protocol","validation","serialization"],"backgroundTag":"schema-validation-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}