Skip to content

feat(phase8 #93 D8.5-FE): consume canonical AgentTurnSnapshot history + render user bubble from input_text#1707

Merged
earayu merged 2 commits into
mainfrom
fe/d8.5-non-agent-uimessage
Apr 25, 2026
Merged

feat(phase8 #93 D8.5-FE): consume canonical AgentTurnSnapshot history + render user bubble from input_text#1707
earayu merged 2 commits into
mainfrom
fe/d8.5-non-agent-uimessage

Conversation

@earayu
Copy link
Copy Markdown
Collaborator

@earayu earayu commented Apr 25, 2026

Summary

D8.5-FE first-cut. Chained on @bryce's #92 PR #1706 (bryce/phase8-task92-d85-be-non-agent-uimessage).

ChatDetails.history flips from list[list[ChatMessage]]list[AgentTurnSnapshot]. The FE consumes the canonical UIMessage at-rest envelope directly: historical turns share the live-turn render path; user bubbles come from snapshot.input_text. Legacy MessagePartsAi rendering branch is gone.

What changed (FE)

File Change
web/src/components/chat/chat-messages.tsx Full per-turn render rewrite: liveTurns map + turnOrder + pendingUserMessages (optimistic). Seeds liveTurns directly from chat.history. AgentTurnStreamCard now renders <MessagePartsUser> from envelope.input_text inline above the AI card.
web/src/features/agent-runtime/api.ts AgentTurnSnapshotEnvelope extended with runtime_kind: AgentRuntimeKind + input_text. AgentRuntimeKind exported.
web/src/features/agent-runtime/index.ts Re-export AgentRuntimeKind.
web/src/api-v2/schema.d.ts Regenerated via yarn api:v2:types against post-#92 OpenAPI.

Boundary held (per PM lock msg=38e116e5)

  • No new chat-runtime/ feature moduleagent-runtime/ covers the historical render path 1:1.
  • runtime_kind is BE-internal — FE does not branch on it.
  • No MessagePartsAi / StoredChatMessagePart deletion — Python-side schema/storage delete is [Features] on premise deployment #80 territory; the FE legacy files have no callers after this PR but stay on disk for the [Features] on premise deployment #80 sweep.
  • No new non-agent SSE / live path — production code already routes everything through agent_runtime; per Bryce msg=27e8ec6d D defer + PM acceptance.

D8.5-BE / D8.5-FE handoff contract

Verification

  • yarn lint clean (one pre-existing no-explicit-any warning in features/providers/server-api.ts unrelated).
  • tsc --noEmit clean for touched files (pre-existing main-branch errors in chat-input.tsx unchanged).
  • yarn dev boots in 2.6s on port 3014; GET /, /auth/signin, /workspace/collections, /workspace all return 200.

Test plan

  • Symphony architect canonical drift quick check on the canonical-history → renderer mapping
  • Optional FE seam re-check (any reviewer): historical render + optimistic user bubble + recoverActiveTurn path
  • Manual smoke once a fresh tenant is up: load a chat with history → confirm historical user bubbles + AI cards render from canonical parts; submit a new turn → confirm pending bubble → live stream → terminal closure all work

Note on chained base

This PR's base is bryce/phase8-task92-d85-be-non-agent-uimessage (PR #1706). Once #1706 lands on main, GitHub will auto-rebase this branch.

🤖 Generated with Claude Code

earayu and others added 2 commits April 26, 2026 02:13
…kind discriminator

Phase 8 task #92 (D8.5-BE) — first-cut backend migration of the
non-agent chat path to the canonical ``UIMessage`` shape, scoped per
architect msg=01918929 + Weston msg=df87fe24 + earayu2 msg=f20d5034
hard-cut acceptance:

The inventory revealed the production "non-agent chat path" the
original D8.5 design assumed has already converged on the agent
runtime (``chat_completion_service.openai_chat_completions`` already
delegates to ``runtime_manager.turn_service.create_or_get_turn`` and
``ChatService.create_chat`` rejects non-AGENT bots). So the actual
#92 work is A+B+C only — adding the discriminator column for future
non-agent paths and migrating the user-visible chat history shape to
canonical UIMessage. The translator extension (``chat.text.delta`` /
``chat.completed``) and the ``StoredChatMessagePart`` /
``RedisChatMessageHistory`` deletion are deferred per architect /
Weston canonical lock.

Changes:

A. ``runtime_kind`` discriminator on ``agent_message`` table
- ``aperag/domains/agent_runtime/db/models.py``: new
  ``runtime_kind: str`` ORM column with values
  ``agent_runtime`` / ``direct_chat`` / ``rag_chat`` (mutually
  exclusive enum); existing rows backfill via
  ``server_default="agent_runtime"``. ``role`` keeps speaker
  semantics independent of the runtime that produced the message.
- ``aperag/migration/versions/...c8f2d34a51e7_add_agent_message_runtime_kind.py``:
  additive migration; downgrade drops the column.

B. ``ChatService._build_v3_chat_history`` rewrite
- Returns ``list[AgentTurnSnapshot]`` (one snapshot per assistant
  turn) instead of the legacy ``list[list[ChatMessage]]`` shape.
- Reuses ``snapshot_assembler.assemble_parts_from_artifacts`` (the
  #90 D8.4d projection) so historical turns expose the same
  ``UIMessagePart`` shape the FE consumes from the live SSE stream
  (D8 §2 wire/at-rest byte-equal).
- ``error_text`` for FAILED / CANCELLED turns surfaces an
  ``error_summary`` artifact's message, falling back to
  ``turn.error_message`` — mirrors the snapshot endpoint contract.
- The turn's user query lives at ``input_text`` on the snapshot
  envelope (rather than as a separate ``role=human`` ChatMessage)
  so the FE renders user/assistant from a single object per turn.
- Legacy ``_extract_artifact_text`` / ``_extract_references`` /
  ``_map_reference_item`` / ``_artifact_type_value`` /
  ``_coerce_timestamp`` helpers are retired alongside the legacy
  shape.

C. ``ChatDetails.history`` schema
- ``aperag/domains/conversation/schemas.py``: ``history`` is now
  ``Optional[list[AgentTurnSnapshot]]`` with explicit description
  citing D8 §2 byte-equal canonical and the new shape.
- The ``conversation.schemas`` ↔ ``agent_runtime.uimessage``
  ↔ ``agent_runtime.schemas`` ↔ ``conversation.schemas`` cycle is
  broken via ``TYPE_CHECKING`` import + a module-level
  ``ChatDetails.model_rebuild()`` hook at the bottom of
  ``conversation/schemas.py``. Pydantic resolves the forward ref at
  load time so the OpenAPI schema is fully populated.
- ``aperag/domains/agent_runtime/uimessage.py``: ``AgentTurnSnapshot``
  gains ``runtime_kind: RuntimeKind`` (default ``"agent_runtime"``)
  and ``input_text: Optional[str]`` so historical turns can render
  the user query without a separate envelope round-trip.
- ``TurnService.get_turn_snapshot`` writes both new fields on the
  live snapshot endpoint so live and historical reload paths match.

D. (deferred) Translator extension for ``chat.text.delta`` /
``chat.completed`` and ``StoredChatMessagePart`` /
``RedisChatMessageHistory`` deletion stay out of #92 per
Weston msg=df87fe24 / PM msg=01918929. The non-agent live path the
extension would have served does not exist in the current
codebase; reintroducing it is a feature task, not a refactor.

Tests:
- ``tests/unit_test/chat/test_chat_service.py`` rewritten:
  * ``test_get_chat_returns_canonical_uimessage_history`` pins the
    new shape (snapshot per turn with text + source-url +
    data-citation parts, runtime_kind, input_text)
  * ``test_get_chat_history_surfaces_error_text_for_failed_turn``
    pins the error_text contract for FAILED turns
  * ``test_get_chat_history_does_not_expose_legacy_chatmessage_shape``
    regression-guard against revert to ``list[list[ChatMessage]]``
- ``tests/unit_test/agent_runtime/test_agent_runtime_v3.py`` updated
  to import ``AgentTurnSnapshot`` from ``agent_runtime.uimessage``
  (the back-compat re-export through ``agent_runtime.schemas`` was
  retired to break the new cycle).

Per D10 §G hard gate 1 (comprehensive grep sweep) ran across
``aperag/`` + ``tests/unit_test/`` + ``tests/e2e_http/hurl/`` +
``tests/e2e_http/scripts/``: only the FE
``web/src/components/chat/chat-messages.tsx`` reads ``chat.history``
in the old shape — that is the explicit hand-off seam for #93
huangheng (per architect msg=6e53a7c4).

Gates: full unit suite 833 / 29 skip / 0 fail; ruff check + format
clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… + render user bubble from input_text

D8.5-FE first-cut, chained on @bryce #92 D8.5-BE
(`bryce/phase8-task92-d85-be-non-agent-uimessage`).

Per architect msg=a92ca060 + PM lock msg=38e116e5 + Bryce handoff
msg=27e8ec6d:
* `ChatDetails.history` is now `AgentTurnSnapshot[]` (canonical
  UIMessage at-rest, byte-equal with the live SSE wire).
* `AgentTurnSnapshot` carries `runtime_kind` (forward-compat
  discriminator, FE does not branch on it per PM lock) +
  `input_text` (user-side bubble content).
* No new non-agent SSE / live path — production code already
  routes everything through `agent_runtime`. The deferred
  `chat.text.delta` / `chat.completed` envelope expansion stays
  out per Bryce msg=27e8ec6d D defer and PM acceptance.

## What changed (FE)

* `web/src/components/chat/chat-messages.tsx` — full rewrite of the
  per-turn render orchestration:
  * State replaces `messages: ChatMessage[][]` with `liveTurns` map +
    `turnOrder: string[]` + `pendingUserMessages: { key, query,
    timestamp }[]`.
  * `chat.history` (canonical `AgentTurnSnapshot[]`) directly seeds
    `liveTurns` at mount via `seedFromHistory()` — no per-turn
    `getAgentTurnSnapshot()` round trip on first render.
  * `seedFromSnapshot()` and `ensureTurnGroups()` (tied to the legacy
    `ChatMessage[][]` shape) are gone; `recordTurn()` replaces them.
  * `handleSendMessage()` adds an optimistic `pendingUserMessages`
    entry until `createAgentTurn` returns the real turn id, then
    promotes the turn into `liveTurns` and drops the pending entry.
  * `recoverActiveTurn` effect still re-fetches the snapshot for the
    sessionStorage-active turn id so a mid-stream reload picks up
    cursor / status drift since page load.
* `AgentTurnStreamCard` now renders `<MessagePartsUser>` from
  `envelope.input_text` inline above the AI card so historical and
  live turns share one render path. The legacy `MessagePartsAi`
  branch is gone (canonical parts handle historical render too).
* `web/src/features/agent-runtime/api.ts` — `AgentTurnSnapshotEnvelope`
  extended with `runtime_kind: AgentRuntimeKind` (`agent_runtime` |
  `direct_chat` | `rag_chat`, default `agent_runtime`) and optional
  `input_text`. `AgentRuntimeKind` re-exported from the feature index.
* `web/src/api-v2/schema.d.ts` — regenerated via `yarn api:v2:types`
  against the post-#92 OpenAPI public spec.

## Boundary held (per PM lock)

* No new `chat-runtime/` feature module — the agent-runtime hook +
  renderer cover the historical render path 1:1 (per architect
  msg=38e116e5 lock).
* `runtime_kind` stays a BE-internal discriminator; FE does not
  branch on it.
* Legacy `MessagePartsAi` / `MessagePartAi` / `StoredChatMessagePart`
  files remain on disk — Python-side schema/storage delete is #80
  territory, and the FE files have no callers after this PR but are
  not removed here (kept for #80 sweep).

## Verification

* `yarn lint` clean (one pre-existing `no-explicit-any` warning in
  `features/providers/server-api.ts` unrelated to this PR).
* `tsc --noEmit` clean for touched files (the four pre-existing
  errors in `chat-input.tsx` are main-baseline noise unchanged here).
* `yarn dev` boots in 2.6s on port 3014; `GET /`, `/auth/signin`,
  `/workspace/collections`, `/workspace` all return 200.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Base automatically changed from bryce/phase8-task92-d85-be-non-agent-uimessage to main April 25, 2026 18:57
@earayu earayu merged commit 6e5deb6 into main Apr 25, 2026
4 checks passed
@earayu earayu deleted the fe/d8.5-non-agent-uimessage branch April 25, 2026 18:59
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