| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 |
- /** Test adapter for the production conversation.details.tool registration. */
- import type { HostDescription } from '@deepseek-ai/dsh-client-connection/client'
- import type { SessionEventEntry } from '@deepseek-ai/dsh-api-session-controller/types'
- import { isJsonValue, type JsonValue } from '@deepseek-ai/dsh-session'
- import type {
- ChatConversationViewNode, ChatSnapshot, ConversationNode, DetailsSlotProps,
- DetailsToolOwnerProps, RunningToolCall, ToolResultNode,
- } from '@deepseek-ai/dsh-client-ui-chat/client'
- import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'
- import { ToolDetails } from '../src/client/tool/ToolDetails.tsx'
- type TrajectorySnapshot = Parameters<Parameters<DetailsSlotProps['useTrajectory']>[0]>[0]
- const emptyTrajectory: TrajectorySnapshot = {
- eventNodes: [],
- eventLocations: new Map(),
- requests: [],
- callSchemas: new Map(),
- partial: null,
- runningCalls: [],
- }
- /** Stable empty Trajectory source for DetailsPanel fixtures. */
- export const useEmptyTrajectory: DetailsSlotProps['useTrajectory'] = selector => selector(emptyTrajectory)
- function jsonFixture(value: unknown): JsonValue {
- if (!isJsonValue(value)) throw new Error('tool event fixture must be lossless JSON')
- return value as JsonValue
- }
- /** Build the canonical Chat slice consumed by Tool rows and details tests. */
- export function toolChatSnapshot(
- settled: readonly ConversationNode[] = [],
- running: readonly RunningToolCall[] = [],
- ): ChatSnapshot {
- const roots = [...settled.filter(node => node.kind === 'tool-result'), ...running]
- const nodes: ChatConversationViewNode[] = roots.map(root => ({
- key: `tool:${root.callId}`,
- kind: 'tool-call',
- id: root.callId,
- target: 'chat',
- anchorSeq: 'kind' in root ? root.seq : Number.MAX_SAFE_INTEGER,
- location: { kind: 'session' },
- visibility: 'visible',
- data: { root },
- }))
- const byKey = new Map(nodes.map(node => [node.key, node]))
- const empty: readonly string[] = []
- return {
- order: nodes.map(node => node.key),
- nodes: {
- get: key => byKey.get(key),
- values: () => nodes,
- },
- locations: {
- getTurn: () => empty,
- getStep: () => empty,
- },
- timeline: { turnOrder: [], turns: new Map() },
- legacy: {
- nodes: settled,
- runningCalls: running,
- partial: null,
- turnTimings: new Map(),
- turnEnds: new Map(),
- },
- }
- }
- /** Build the Session event window that projects settled root Tool calls into Chat. */
- export function toolSessionEvents(nodes: readonly ToolResultNode[]): readonly SessionEventEntry[] {
- const firstTime = nodes[0]?.callTime ?? nodes[0]?.time ?? 0
- const entries: SessionEventEntry[] = [
- {
- event: {
- seq: 1,
- time: firstTime - 2,
- type: 'turn/start',
- data: { turn: 1 },
- },
- },
- {
- event: {
- seq: 2,
- time: firstTime - 1,
- type: 'step/start',
- data: { turn: 1, step: 1 },
- },
- },
- ]
- for (const [index, node] of nodes.entries()) {
- if (node.call === null) throw new Error(`tool fixture "${node.callId}" requires its call event`)
- const callSeq = 3 + index * 2
- const callEntry: SessionEventEntry = {
- event: {
- seq: callSeq,
- time: node.callTime ?? node.time - 1,
- type: 'tool/call',
- data: {
- turn: 1,
- step: 1,
- callId: node.callId,
- name: node.call.name,
- arguments: node.call.argsRaw,
- },
- },
- }
- entries.push(callEntry)
- const resultEntry: SessionEventEntry = {
- event: {
- seq: callSeq + 1,
- time: node.time,
- type: 'tool/result',
- data: jsonFixture({
- turn: 1,
- step: 1,
- message: {
- id: `result-${node.callId}`,
- role: 'user',
- source: { kind: 'tool', callId: node.callId },
- content: [{
- type: 'tool-result',
- toolCallId: node.callId,
- content: node.content.map(block => ({ ...block })),
- isError: node.isError,
- }],
- },
- ...(node.error === undefined ? {} : { error: node.error }),
- ...(node.meta === undefined ? {} : { meta: node.meta }),
- }),
- surfaceOp: 'append',
- },
- }
- entries.push(resultEntry)
- }
- return entries
- }
- /**
- * Bind ui-tool's details renderer to the conversation slot callback shape.
- * @param t - conversation locale seat used by Tool cards.
- * @param description - optional Host description so the details card can abbreviate home paths.
- * @returns a direct-test renderSlot implementation.
- */
- export function renderToolDetails(
- t: TranslateNS<'conversation'>,
- description?: HostDescription,
- ): DetailsSlotProps['renderSlot'] {
- return (_key, owner) => {
- // PropsRenderSlots keeps its key generic even for this one-key share;
- // recover the concrete owner selected by the adapter's fixed slot.
- const details = owner as unknown as DetailsToolOwnerProps
- return <ToolDetails
- block={details.block}
- cwd={details.cwd}
- useHostDescription={selector => selector(description)}
- t={t}
- />
- }
- }
|