feat(ui): expand request trajectory inspection

This commit is contained in:
_Kerman
2026-07-27 18:40:30 +08:00
parent ffd2da6986
commit c5be25781c
10 changed files with 522 additions and 151 deletions

View File

@@ -28,7 +28,8 @@ export type {
EngineStoreHandle, EngineStoreInstance, ObservableSnapshot, SnapshotStore,
} from './contract/store.ts'
export type {
AssistantBlock, AssistantMessageNode, AssistantTiming, CodeSubCall, ComposerPhase, ContextMessageNode,
AssistantBlock, AssistantMessageNode, AssistantProvenanceView, AssistantRequestConfig,
AssistantTiming, CodeSubCall, ComposerPhase, ContextMessageNode,
ConversationContext, ConversationContextOriginKind, ConversationNode, ConversationPromptSnapshot,
ConversationSnapshot, QueuedMessage, RunningToolCall,
SteeringMessageNode, ToolResultNode, UnknownSurfaceNode, UserMessageNode,

View File

@@ -60,6 +60,23 @@ export interface AssistantTiming {
completedTime: number
}
/** Request configuration recorded in the effective header for one assistant response. */
export interface AssistantRequestConfig {
provider: string
model: string
thinking?: string
reasoningEffort?: string
temperature?: number
maxTokens?: number
stop?: readonly string[]
}
/** Stable provider/model identity attached to one assistant response. */
export interface AssistantProvenanceView {
provider: string
model: string
}
/** A finalized (or interruption-frozen) assistant message. */
export interface AssistantMessageNode {
kind: 'assistant'
@@ -70,6 +87,8 @@ export interface AssistantMessageNode {
step: number
blocks: readonly AssistantBlock[]
usage?: unknown
provenance?: AssistantProvenanceView
requestConfig?: AssistantRequestConfig
/** Timing derived from the recorded step/chunk/message event sequence. */
timing?: AssistantTiming
/** Frozen partial of an aborted turn (no finalize ever arrives): rendered with a 已停止 marker.
@@ -211,6 +230,8 @@ export type ConversationContextOriginKind = 'compaction' | 'rewind' | 'rewrite'
/** Latest complete model request header in force within one context generation. */
export interface ConversationPromptSnapshot {
/** Provider/model and sampling configuration from the latest effective request header. */
config?: AssistantRequestConfig
/** Rendered system prompt text; empty when the request had no system prompt. */
system: string
/** Complete tool catalog sent with the request, including tools that were never called. */

View File

@@ -12,7 +12,7 @@ import {
} from '@deepseek-ai/dsh-session/surface'
import type { ToolCallView, ToolEventView, ToolResultView } from '@deepseek-ai/dsh-client-connection/client'
import type {
AssistantTiming, ConversationContext, ConversationContextOriginKind, ConversationNode,
AssistantRequestConfig, AssistantTiming, ConversationContext, ConversationContextOriginKind, ConversationNode,
ConversationPromptSnapshot,
} from './conversation.ts'
import { toAssistantBlocks } from './conversation.ts'
@@ -43,6 +43,7 @@ function materializeNode(
callIndex: ReadonlyMap<string, CallIndexEntry>,
resultView: ToolResultView | null,
assistantTiming?: AssistantTiming,
requestConfig?: AssistantRequestConfig,
): ConversationNode {
switch (event.type) {
case 'user/message':
@@ -64,6 +65,11 @@ function materializeNode(
kind: 'assistant', seq: event.seq, time: event.time,
turn: event.data.turn, step: event.data.step,
blocks: toAssistantBlocks(event.data.content), usage: event.data.usage,
provenance: {
provider: event.data.provenance.provider,
model: event.data.provenance.model,
},
...(requestConfig === undefined ? {} : { requestConfig }),
...(assistantTiming !== undefined ? { timing: assistantTiming } : {}),
}
case 'steering/message':
@@ -204,6 +210,7 @@ export class FoldAdapter {
this.callIdx,
this.resultViews.get(seq) ?? null,
event.type === 'assistant/message' ? this.assistantTiming(event) : undefined,
event.type === 'assistant/message' ? this.assistantRequestConfig(event) : undefined,
)
this.nodeCache.set(seq, node)
out.push(node)
@@ -280,6 +287,7 @@ export class FoldAdapter {
this.callIdx,
this.resultViews.get(seq) ?? null,
event.type === 'assistant/message' ? this.assistantTiming(event) : undefined,
event.type === 'assistant/message' ? this.assistantRequestConfig(event) : undefined,
)
this.nodeCache.set(seq, node)
return node
@@ -312,6 +320,17 @@ export class FoldAdapter {
return { stepStartTime, firstTokenTime, completedTime: event.time }
}
private assistantRequestConfig(
event: SessionEvent<'assistant/message'>,
): AssistantRequestConfig | undefined {
for (let i = event.seq; i >= this.baseSeq; i--) {
const candidate = this.padded[i]
if (candidate?.type !== 'request/header') continue
return candidate.data.header.config
}
return undefined
}
private indexCall(event: SessionEvent, view?: ToolEventView): void {
if (event.type === 'tool/result') {
if (view?.for === 'result') this.resultViews.set(event.seq, view.view)
@@ -336,6 +355,7 @@ export class FoldAdapter {
}
if (event.type !== 'request/header') return
this.activePrompt = {
config: event.data.header.config,
system: event.data.header.system ?? '',
tools: event.data.header.tools ?? [],
}