refactor(session-query): split model-facing tool modules
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
/**
|
||||
* Model text rendering and generic tool-call presentation.
|
||||
*
|
||||
* @module @deepseek-ai/dsh-tool-session-query/presentation
|
||||
*/
|
||||
|
||||
import {
|
||||
extractSessionEventText,
|
||||
type SessionEventSearchHit,
|
||||
type SessionEventTraceObservation,
|
||||
type SessionEventWindow,
|
||||
type SessionLineageTrace,
|
||||
type SessionRecord,
|
||||
type SessionSearchHit,
|
||||
} from '@deepseek-ai/dsh-session-query'
|
||||
import type {
|
||||
SessionEvent,
|
||||
SessionId,
|
||||
} from '@deepseek-ai/dsh-session'
|
||||
import type { GenericCallView } from '@deepseek-ai/dsh-tools'
|
||||
import { workspaceAccess } from './workspace-access.ts'
|
||||
|
||||
type TitleView = Awaited<ReturnType<typeof workspaceAccess.readTitle>>
|
||||
type CompleteTitleMap = Awaited<ReturnType<typeof workspaceAccess.readTitles>>
|
||||
type AuthorizedDescendants = ReturnType<typeof workspaceAccess.authorizeDescendants>
|
||||
|
||||
interface SearchCollection<T> {
|
||||
readonly items: T[]
|
||||
readonly capped: boolean
|
||||
}
|
||||
|
||||
interface SessionSearchCallArgs {
|
||||
readonly query: string
|
||||
}
|
||||
|
||||
interface EventSearchCallArgs {
|
||||
readonly query: string
|
||||
}
|
||||
|
||||
interface SessionTargetCallArgs {
|
||||
readonly session_id?: string
|
||||
}
|
||||
|
||||
interface EventTargetCallArgs extends SessionTargetCallArgs {
|
||||
readonly seq: number
|
||||
}
|
||||
|
||||
function formatSessionSearch(
|
||||
collected: SearchCollection<SessionSearchHit>,
|
||||
titles: CompleteTitleMap,
|
||||
authorizedParents: ReadonlySet<SessionId>,
|
||||
): string {
|
||||
if (collected.items.length === 0) return formatEmptySessionSearch()
|
||||
const lines = [`Session search results (${collected.items.length}):`]
|
||||
for (const [index, hit] of collected.items.entries()) {
|
||||
const parent = hit.header.parentSession === undefined
|
||||
? 'root'
|
||||
: authorizedParents.has(hit.header.parentSession)
|
||||
? hit.header.parentSession
|
||||
: '[outside workspace]'
|
||||
const availability = [
|
||||
hit.live ? 'live' : undefined,
|
||||
hit.persisted ? 'persisted' : undefined,
|
||||
].filter((value): value is string => value !== undefined).join(', ') || 'unavailable'
|
||||
lines.push(
|
||||
'',
|
||||
`${index + 1}. Session ${hit.header.id} — ${workspaceAccess.titleText(titles.get(hit.header.id))}`,
|
||||
` Created: ${formatTime(hit.header.createdAt)}`,
|
||||
` Parent: ${parent}`,
|
||||
` Availability: ${availability}`,
|
||||
` Best match: seq ${hit.bestMatch.seq} | ${hit.bestMatch.type} | ${hit.bestMatch.surface} | ${formatTime(hit.bestMatch.time)}`,
|
||||
` Snippet: ${hit.bestMatch.snippet}`,
|
||||
)
|
||||
}
|
||||
if (collected.capped) {
|
||||
lines.push('', 'Result cap reached. Narrow the query or add filters to find additional matches.')
|
||||
}
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function formatEmptySessionSearch(): string {
|
||||
return 'No prior session matches found.'
|
||||
}
|
||||
|
||||
function formatEventSearch(
|
||||
sessionId: SessionId,
|
||||
title: TitleView,
|
||||
collected: SearchCollection<SessionEventSearchHit>,
|
||||
): string {
|
||||
const lines = [`Session ${sessionId} — ${workspaceAccess.titleText(title)}`]
|
||||
if (collected.items.length === 0) {
|
||||
lines.push('', 'No prior event matches found.')
|
||||
return lines.join('\n')
|
||||
}
|
||||
lines.push('', `Event search results (${collected.items.length}):`)
|
||||
for (const [index, hit] of collected.items.entries()) {
|
||||
lines.push(
|
||||
`${index + 1}. seq ${hit.seq} | ${hit.type} | ${hit.surface} | ${formatTime(hit.time)}`,
|
||||
` Snippet: ${hit.snippet}`,
|
||||
)
|
||||
}
|
||||
if (collected.capped) {
|
||||
lines.push('', 'Result cap reached. Narrow the query or add filters to find additional matches.')
|
||||
}
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function formatSessionTrace(
|
||||
trace: SessionLineageTrace,
|
||||
ancestors: readonly SessionRecord[],
|
||||
ancestorBoundary: boolean,
|
||||
descendants: AuthorizedDescendants,
|
||||
titles: CompleteTitleMap,
|
||||
): string {
|
||||
const lines = [
|
||||
`Session ${trace.target.header.id} — ${workspaceAccess.titleText(titles.get(trace.target.header.id))}`,
|
||||
`Created: ${formatTime(trace.target.header.createdAt)}`,
|
||||
`Availability: ${availabilityText(trace.target)}`,
|
||||
'',
|
||||
'Ancestors (nearest first):',
|
||||
]
|
||||
if (ancestors.length === 0 && !ancestorBoundary) lines.push('- none (target is a root session)')
|
||||
for (const record of ancestors) {
|
||||
lines.push(`- ${record.header.id} — ${workspaceAccess.titleText(titles.get(record.header.id))} | ${formatTime(record.header.createdAt)} | ${availabilityText(record)}`)
|
||||
}
|
||||
if (ancestorBoundary) lines.push('- [outside workspace boundary]')
|
||||
lines.push('', 'Descendants:')
|
||||
if (descendants.length === 0) lines.push('- none')
|
||||
else renderDescendants(lines, descendants, titles)
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function renderDescendants(
|
||||
lines: string[],
|
||||
nodes: AuthorizedDescendants,
|
||||
titles: CompleteTitleMap,
|
||||
): void {
|
||||
for (const { node, depth } of workspaceAccess.visitDescendants(nodes)) {
|
||||
const indent = ' '.repeat(depth)
|
||||
if (node === null) {
|
||||
lines.push(`${indent}- [outside workspace subtree]`)
|
||||
continue
|
||||
}
|
||||
const id = node.record.header.id
|
||||
lines.push(`${indent}- ${id} — ${workspaceAccess.titleText(titles.get(id))} | ${formatTime(node.record.header.createdAt)} | ${availabilityText(node.record)}`)
|
||||
}
|
||||
}
|
||||
|
||||
function formatEventTrace(
|
||||
sessionId: SessionId,
|
||||
title: TitleView,
|
||||
trace: SessionEventTraceObservation,
|
||||
): string {
|
||||
return [
|
||||
`Session ${sessionId} — ${workspaceAccess.titleText(title)}`,
|
||||
`Target: seq ${trace.target.seq} | ${trace.target.type} | ${trace.target.surface} | ${formatTime(trace.target.time)}`,
|
||||
`Replaced by: ${trace.replacedBy ?? 'none'}`,
|
||||
`Replacement chain: ${seqList(trace.replacementChain)}`,
|
||||
`Events replaced by target: ${seqList(trace.replacedEventSeqs)}`,
|
||||
`Direct provenance sources: ${seqList(trace.sourceEventSeqs)}`,
|
||||
`Direct derived events: ${seqList(trace.derivedEventSeqs)}`,
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
function formatEventRead(
|
||||
sessionId: SessionId,
|
||||
title: TitleView,
|
||||
window: SessionEventWindow,
|
||||
): string {
|
||||
const before = window.events.filter(event => event.seq < window.target.seq)
|
||||
const after = window.events.filter(event => event.seq > window.target.seq)
|
||||
const lines = [
|
||||
`Session ${sessionId} — ${workspaceAccess.titleText(title)}`,
|
||||
`Target event seq ${window.target.seq}:`,
|
||||
'```json',
|
||||
JSON.stringify(window.target, null, 2),
|
||||
'```',
|
||||
]
|
||||
if (before.length > 0) {
|
||||
lines.push('', 'Before:')
|
||||
for (const event of before) lines.push(formatNeighbor(event))
|
||||
}
|
||||
if (after.length > 0) {
|
||||
lines.push('', 'After:')
|
||||
for (const event of after) lines.push(formatNeighbor(event))
|
||||
}
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function formatNeighbor(event: SessionEvent): string {
|
||||
const text = extractSessionEventText(event)
|
||||
return `- seq ${event.seq} | ${event.type} | ${formatTime(event.time)}`
|
||||
+ (text.length === 0 ? ' | (no semantic text)' : `\n ${text.replaceAll('\n', '\n ')}`)
|
||||
}
|
||||
|
||||
function availabilityText(record: SessionRecord): string {
|
||||
return [
|
||||
record.live ? 'live' : undefined,
|
||||
record.persisted ? 'persisted' : undefined,
|
||||
].filter((value): value is string => value !== undefined).join(', ') || 'unavailable'
|
||||
}
|
||||
|
||||
function seqList(values: readonly number[]): string {
|
||||
return values.length === 0 ? 'none' : values.join(', ')
|
||||
}
|
||||
|
||||
function formatTime(value: number): string {
|
||||
return new Date(value).toISOString()
|
||||
}
|
||||
|
||||
function presentSessionSearchCall(args: SessionSearchCallArgs): GenericCallView {
|
||||
return { card: 'generic', kind: 'search', title: 'Search prior sessions', rawInput: args.query }
|
||||
}
|
||||
|
||||
function presentEventSearchCall(args: EventSearchCallArgs): GenericCallView {
|
||||
return { card: 'generic', kind: 'search', title: 'Search session events', rawInput: args.query }
|
||||
}
|
||||
|
||||
function presentSessionTraceCall(args: SessionTargetCallArgs): GenericCallView {
|
||||
return {
|
||||
card: 'generic',
|
||||
kind: 'read',
|
||||
title: args.session_id === undefined ? 'Trace current session' : `Trace session ${args.session_id}`,
|
||||
...args.session_id === undefined ? {} : { rawInput: args.session_id },
|
||||
}
|
||||
}
|
||||
|
||||
function presentEventTargetCall(
|
||||
action: string,
|
||||
args: EventTargetCallArgs,
|
||||
): GenericCallView {
|
||||
return {
|
||||
card: 'generic',
|
||||
kind: 'read',
|
||||
title: `${action} ${args.seq}`,
|
||||
rawInput: {
|
||||
...args.session_id === undefined ? {} : { session_id: args.session_id },
|
||||
seq: args.seq,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/** Text output and call-card presentation for every session-query tool. */
|
||||
export const presentation = {
|
||||
formatSessionSearch,
|
||||
formatEmptySessionSearch,
|
||||
formatEventSearch,
|
||||
formatSessionTrace,
|
||||
formatEventTrace,
|
||||
formatEventRead,
|
||||
presentSessionSearchCall,
|
||||
presentEventSearchCall,
|
||||
presentSessionTraceCall,
|
||||
presentEventTargetCall,
|
||||
}
|
||||
Reference in New Issue
Block a user