feat(chat): add the raw llm request viewer #342

Merged
rwachters merged 2 commits from feat/raw-llm-request-viewer into master 2026-10-11 22:01:26 +00:00
rwachters commented 2026-10-11 22:00:11 +00:00 (Migrated from github.com)

Summary

Lets the owner of a chat session inspect the exact provider request the server prepared for the latest assistant step, so a surprising reply can be traced back to what was actually sent.

Changes

  • Added a provider-neutral RawLlmRequestPayload (method, url) and a raw_llm_request informational event on both the non-streaming and streaming chat surfaces. A step that issues several requests emits one event per request, and the latest one wins.

  • Captured the prepared request in the LLM client path through an onRequestPrepared observer, forwarded through the retrying decorator, the turn runners and the transport mappers; the streaming client is now suspending so the observer runs in the turn's own coroutine before dispatch.

  • Masked the credential before any event exists: the URL and every header value get the same min(8, length/2)-prefix masking, a short key becomes ****, and no Authorization header is synthesized for a keyless provider.

  • Added an app-side capture slot with overwrite semantics, cleared by a state reset and by an accepted send, plus a read-only dialog with an explicit empty state, a Copy action and a top-bar menu entry.

  • Copied document contains only the four wire fields; the body size shown in the dialog is computed client-side from the body.

Implementation notes

  • The new payload and the new event subtypes are an additive wire change that requires a lock-step server and client upgrade: an older client cannot decode the new event subtypes.

  • A capture never affects the transcript: the repository treats both new events as diagnostic no-ops that leave the cached session untouched.

  • The capture must never break the completion it describes, so a non-cancellation failure inside the observer is logged and swallowed while cancellation is rethrown.

  • The body is forwarded verbatim and is not masked; all current strategies send a pre-serialized body that contains no credential.

Validation

  • ./gradlew :server:testClasses :server:test

  • ./gradlew :app:desktopMainClasses :app:desktopTestClasses :app:desktopTest

Risks and follow-up

  • Android and WebAssembly targets were not compiled in the validation environment; no exhaustive when over the touched sealed types exists in androidMain or wasmJsMain.

  • Follow-up candidates deliberately left out of this change: the mapper KDoc overstates body masking, the accepted/refused-send and tool-loop paths have no dedicated tests, and the copied body relies on Any.toString() for non-string bodies.

## Summary Lets the owner of a chat session inspect the exact provider request the server prepared for the latest assistant step, so a surprising reply can be traced back to what was actually sent. ## Changes - Added a provider-neutral `RawLlmRequestPayload` (method, url) and a `raw_llm_request` informational event on both the non-streaming and streaming chat surfaces. A step that issues several requests emits one event per request, and the latest one wins. - Captured the prepared request in the LLM client path through an `onRequestPrepared` observer, forwarded through the retrying decorator, the turn runners and the transport mappers; the streaming client is now suspending so the observer runs in the turn's own coroutine before dispatch. - Masked the credential before any event exists: the URL and every header value get the same `min(8, length/2)`-prefix masking, a short key becomes `****`, and no `Authorization` header is synthesized for a keyless provider. - Added an app-side capture slot with overwrite semantics, cleared by a state reset and by an accepted send, plus a read-only dialog with an explicit empty state, a Copy action and a top-bar menu entry. - Copied document contains only the four wire fields; the body size shown in the dialog is computed client-side from the body. ## Implementation notes - The new payload and the new event subtypes are an additive wire change that requires a lock-step server and client upgrade: an older client cannot decode the new event subtypes. - A capture never affects the transcript: the repository treats both new events as diagnostic no-ops that leave the cached session untouched. - The capture must never break the completion it describes, so a non-cancellation failure inside the observer is logged and swallowed while cancellation is rethrown. - The body is forwarded verbatim and is not masked; all current strategies send a pre-serialized body that contains no credential. ## Validation - `./gradlew :server:testClasses :server:test` - `./gradlew :app:desktopMainClasses :app:desktopTestClasses :app:desktopTest` ## Risks and follow-up - Android and WebAssembly targets were not compiled in the validation environment; no exhaustive `when` over the touched sealed types exists in `androidMain` or `wasmJsMain`. - Follow-up candidates deliberately left out of this change: the mapper KDoc overstates body masking, the accepted/refused-send and tool-loop paths have no dedicated tests, and the copied body relies on `Any.toString()` for non-string bodies.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Torvian.eu/chatbot!342
No description provided.