Skip to content

feat(chat): Phase 3 PR-B — chat HTTP + SSE + replay (text-only)#23

Merged
Leolebleis merged 12 commits intomainfrom
feat/chat-pr-b
May 2, 2026
Merged

feat(chat): Phase 3 PR-B — chat HTTP + SSE + replay (text-only)#23
Leolebleis merged 12 commits intomainfrom
feat/chat-pr-b

Conversation

@Leolebleis
Copy link
Copy Markdown
Owner

Summary

Lands the user-facing chat backend on top of PR-A's foundation.

  • POST /chat/messages — text-only SSE stream. 7 typed wire events: text_delta, tool_call_start, tool_call_delta, tool_call_result, thinking_delta, done, error.
  • GET /chat/messages — replay with limit + before_id cursor pagination.
  • ChatAgent Protocol seam in chat/client.pyChatService and router.py stay pydantic-ai-free.
  • pydantic-ai bumped to >=1.89,<2; added anthropic_cache_instructions='1h' to build_chat_agent.
  • Three architecture deferrals from PR-A review resolved: Protocol seam, get_thread 404-only-on-cursor-miss, before_id cursor pagination.

Stack notes

  • Streaming: agent.run_stream_events(prompt, message_history=...) (pydantic-ai 1.89 — AgentRunResultEvent moved to top-level pydantic_ai, mapper adapted).
  • Caching: instructions + tool definitions both at 1h. PR-C will use the remaining 2 of Anthropic's 4 cache breakpoints for CachePoint-driven hot-state injection.
  • History bounded at 200 messages via load_history. Token-aware truncation deferred.
  • Cursor pagination via (created_at, rowid) row-value comparison, resolved through repo.get_cursor.

Test plan

  • uv run pytest — 410 passing (376 baseline + 34 new chat/e2e tests)
  • uv run ruff check --no-cache src/ tests/ clean
  • uv run ty check src/ clean
  • Coverage 95.53%
  • e2e: POST → SSE → GET replay end-to-end with TestModel
  • e2e: mid-stream error keeps user-row, no assistant-row, ends with error event
  • Router tests: 7-event SSE sequence, 422 on empty text, 404 on unknown before_id
  • Service tests: stream_message coordination, NotFoundError on cursor miss, history forwarded

Out of scope (PR-C)

  • Multimodal BinaryContent user input
  • Hot-state pre-injection with CachePoint
  • _mcp_server.read_resource() workaround for chat
  • Multi-thread API surface
  • Token-aware history truncation

Leolebleis added 12 commits May 2, 2026 10:15
- Replace `repo.get_message + raw _conn.execute(rowid)` with `repo.get_cursor`;
  kills the SLF001/ty-ignore reach-through and the production assert.
- Replace 7-case match in `_event_to_sse` with `type(event).__name__` →
  snake_case + `asdict`; mirrors `events/router.py` and removes PLR0911.
- Convert `chat_message_to_response` to `ChatMessageMapper.to_api_response`
  matching `JournalMapper`/`BagMapper` style.
- Drop `_to_jsonable` wrapping; widen `ToolCallResult.result` to `Any`
  (preserve raw shape on the wire — dict/str/list/None).
- Use `ModelMessagesTypeAdapter.dump_python(mode="json")` instead of
  `dump_json → decode → json.loads` round-trip in service + mapper.
- Drop `if history else []` short-circuit — `validate_python([])` is fine.
- Drop dead `and messages` from `next_before_id` guard.
- Reuse `fellow_mock` fixture from `tests/e2e/conftest.py`.
- Extract `parse_sse` / `parse_sse_async` to `tests/_sse.py`.
- Simplify `make_fake_chat_agent` (drop dead mid-loop branch; rename to
  `raise_at_end`).
- Use `NotFoundError.for_resource` consistently in tests.
- Delete narrating WHAT-comments in service.py.

All 410 tests green; coverage 95.53%.
@Leolebleis Leolebleis merged commit c7ce87d into main May 2, 2026
3 checks passed
@Leolebleis Leolebleis deleted the feat/chat-pr-b branch May 2, 2026 09:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant