~bigbes/lethe

ref: 0555d5d52feb15256352adfa3f2a4d62835c7dac lethe/web/src/features/session/useSession.ts -rw-r--r-- 3.2 KiB
0555d5d5 — Eugene Blikh chore: add BSD-2-Clause license 6 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import { useQuery } from '@tanstack/react-query'
import type { UseQueryResult } from '@tanstack/react-query'
import { apiFetch } from '../../api/client'
import { adaptSession } from '../../api/adapters'
import type { Session, SessionDTO } from '../../api/adapters'

export interface TurnDTO {
  turn_id: string
  seq: number
  role: 'user' | 'assistant' | 'tool' | 'system'
  timestamp: number
  content: string
  model?: string
  tokens_in?: number
  tokens_out?: number
  cost_usd?: number
  tool_calls?: unknown
  metadata?: unknown
}

export interface Turn {
  i: number
  role: 'user' | 'assistant' | 'tool'
  body: string
  model?: string
  tokensIn?: number
  tokensOut?: number
  toolName?: string
  toolKind?: 'call' | 'result'
  toolOutput?: string
}

export interface SessionWithTurnsDTO extends SessionDTO {
  turns: TurnDTO[]
}

export interface SessionWithTurns extends Omit<Session, 'turns'> {
  turns: Turn[]
}

function extractToolCall(
  raw: unknown,
): { toolName?: string; toolKind?: 'call' | 'result'; toolOutput?: string } {
  if (raw == null || typeof raw !== 'object') {
    return {}
  }
  const obj = raw as Record<string, unknown>
  // opencode format: {tool, status, output}
  const opencodeTool = typeof obj['tool'] === 'string' ? obj['tool'] : undefined
  const opencodeStatus = typeof obj['status'] === 'string' ? obj['status'] : undefined
  if (opencodeTool != null) {
    const kind = opencodeStatus === 'completed' ? 'result' : 'call'
    const output = typeof obj['output'] === 'string' ? obj['output'] : undefined
    return { toolName: opencodeTool, toolKind: kind as 'call' | 'result', toolOutput: output }
  }
  // claude-code format: {name, type: "tool_use"|"tool_result"}
  const name = typeof obj['name'] === 'string' ? obj['name'] : undefined
  const type = typeof obj['type'] === 'string' ? obj['type'] : undefined
  const kind =
    type === 'tool_use' ? 'call' as const
    : type === 'tool_result' ? 'result' as const
    : undefined
  if (name != null && kind != null) {
    const output = typeof obj['content'] === 'string' ? obj['content'] : undefined
    return { toolName: name, toolKind: kind, toolOutput: output }
  }
  return {}
}

function extractFirstToolCall(
  raw: unknown,
): { toolName?: string; toolKind?: 'call' | 'result'; toolOutput?: string } {
  if (Array.isArray(raw) && raw.length > 0) {
    return extractToolCall(raw[0])
  }
  return extractToolCall(raw)
}

export function adaptTurn(d: TurnDTO): Turn {
  const role: 'user' | 'assistant' | 'tool' =
    d.role === 'system' ? 'assistant' : d.role
  const { toolName, toolKind, toolOutput } = extractFirstToolCall(d.tool_calls)
  return {
    i: d.seq,
    role,
    body: d.content,
    model: d.model,
    tokensIn: d.tokens_in,
    tokensOut: d.tokens_out,
    toolName,
    toolKind,
    toolOutput,
  }
}

export function useSession(
  tool: string,
  host: string,
  id: string,
): UseQueryResult<SessionWithTurns> {
  return useQuery({
    queryKey: ['session', tool, host, id],
    queryFn: async () => {
      const dto = await apiFetch<SessionWithTurnsDTO>(
        `/api/v1/sessions/${tool}/${host}/${id}`,
      )
      const session = adaptSession(dto)
      return { ...session, turns: dto.turns.map(adaptTurn) }
    },
  })
}