can1357/oh-my-pi · error · ValueError

assistantMessageEvent.content must be a string

Error message

assistantMessageEvent.content must be a string

What it means

End events must carry the finalized block content as a string. The parser raises this error when the 'content' key is absent or is not a string, so AssistantTextEndEvent/AssistantThinkingEndEvent always expose the complete text.

Source

Thrown at python/omp-rpc/src/omp_rpc/protocol.py:410

        return AssistantToolCallDeltaEvent(
            type="toolcall_delta",
            contentIndex=content_index,
            delta=delta,
            partial=partial,
        )
    if event_type in {"text_end", "thinking_end"}:
        partial = _parse_assistant_message(
            _clone_json_object(
                payload.get("partial"), field="assistantMessageEvent.partial"
            ),
            field="assistantMessageEvent.partial",
        )
        content_index = _optional_int(payload, "contentIndex")
        content = _optional_str(payload, "content")
        if content_index is None:
            raise ValueError("assistantMessageEvent.contentIndex must be an integer")
        if content is None:
            raise ValueError("assistantMessageEvent.content must be a string")
        if event_type == "text_end":
            return AssistantTextEndEvent(
                type="text_end",
                contentIndex=content_index,
                content=content,
                partial=partial,
            )
        return AssistantThinkingEndEvent(
            type="thinking_end",
            contentIndex=content_index,
            content=content,
            partial=partial,
        )
    if event_type == "toolcall_end":
        partial = _parse_assistant_message(
            _clone_json_object(
                payload.get("partial"), field="assistantMessageEvent.partial"
            ),

View on GitHub (pinned to 9690622007)

Solutions

  1. Include the full accumulated content string in end events
  2. Fix the emitter to accumulate deltas and populate content on end
  3. If the full content is unavailable, accumulate text_delta fragments client-side and synthesize a valid event
  4. Upgrade mismatched protocol implementations

Example fix

// before
{"type": "text_end", "contentIndex": 0}
// after
{"type": "text_end", "contentIndex": 0, "content": "full text"}
Defensive patterns

Strategy: validation

Validate before calling

if event.get("type") in {"text_end", "thinking_end"} and not isinstance(event.get("content"), str):
    raise ValueError("end event missing string content")

Type guard

def has_string_content(e: dict) -> bool:
    return isinstance(e.get("content"), str)

Try / catch

try:
    event = parse_assistant_message_event(payload)
except ValueError as e:
    logger.warning("end event without content: %s", e)
    event = None  # optionally reconstruct from accumulated deltas

Prevention

When it happens

Trigger: text_end/thinking_end notification missing 'content', or with content as an object/array/null; an emitter that only sends deltas and skips the final content accumulation.

Common situations: Custom bridges that emit end markers without accumulating full text; providers whose end event uses a differently named field; protocol version mismatch.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/33411a5db4a20e28. Report an issue: GitHub.