mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: stream advisor tool output (#25032)
Stream advisor output into the advisor tool card while the nested advisor call is still running. This keeps the advisor implementation intentionally advisor-specific: the parent model still receives the same final structured tool result, while the frontend receives transient `tool-result.result_delta` parts to render partial advisor text in the expanded card. The final persisted chat history remains unchanged. Refs CODAGT-322. Generated by Coder Agents. <details> <summary>Implementation plan</summary> - Publish advisor text deltas from the nested `chatloop.Run` via `RunAdvisorOptions.OnAdviceDelta`. - Forward those deltas through `chatadvisor.Tool` with the parent advisor tool call ID. - Emit transient `ChatMessagePartTypeToolResult` websocket parts with `ResultDelta` from `chatd`. - Add `result_delta` to the generated tool-result TypeScript variant. - Accumulate tool result deltas in frontend stream state and keep the tool running until the final result arrives. - Render streamed advisor advice in the existing advisor card using streaming markdown mode, while retaining the updated advisor UI. </details>
This commit is contained in:
+7
-4
@@ -255,7 +255,8 @@ type ChatMessagePart struct {
|
||||
Args json.RawMessage `json:"args,omitempty" variants:"tool-call?"`
|
||||
ArgsDelta string `json:"args_delta,omitempty" variants:"tool-call?"`
|
||||
Result json.RawMessage `json:"result,omitempty" variants:"tool-result?"`
|
||||
ResultDelta string `json:"result_delta,omitempty"`
|
||||
ResultDelta string `json:"result_delta,omitempty" variants:"tool-result?"`
|
||||
ResultReset bool `json:"result_reset,omitempty" variants:"tool-result?"`
|
||||
IsError bool `json:"is_error,omitempty" variants:"tool-result?"`
|
||||
IsMedia bool `json:"is_media,omitempty" variants:"tool-result?"`
|
||||
SourceID string `json:"source_id,omitempty" variants:"source?"`
|
||||
@@ -327,9 +328,11 @@ type ChatMessagePart struct {
|
||||
// StripInternal removes internal-only fields that must not be
|
||||
// sent to API clients. Call before publishing via REST or SSE.
|
||||
//
|
||||
// Note: ArgsDelta and ResultDelta are intentionally preserved.
|
||||
// They are streaming-only fields consumed by the frontend via
|
||||
// SSE message_part events (see processStepStream in chatloop).
|
||||
// Note: ArgsDelta, ResultDelta, and ResultReset are intentionally preserved.
|
||||
// They are streaming-only fields consumed by the frontend via SSE
|
||||
// message_part events. ArgsDelta is produced by processStepStream in
|
||||
// chatloop; ResultDelta and ResultReset are produced by the advisor
|
||||
// streaming callbacks in chatd.
|
||||
func (p *ChatMessagePart) StripInternal() {
|
||||
p.ProviderMetadata = nil
|
||||
if p.FileID.Valid {
|
||||
|
||||
@@ -264,7 +264,6 @@ func TestChatMessagePartVariantTags(t *testing.T) {
|
||||
excludedFields := map[string]string{
|
||||
"type": "discriminant, added automatically by codegen",
|
||||
"signature": "added in #22290, never populated by any code path",
|
||||
"result_delta": "added in #22290, never populated by any code path",
|
||||
"provider_metadata": "internal only, stripped by db2sdk before API responses",
|
||||
"context_file_content": "internal only, stripped before API responses (typescript:\"-\")",
|
||||
"context_file_os": "internal only, used during prompt expansion (typescript:\"-\")",
|
||||
|
||||
Reference in New Issue
Block a user