feat: add assistant metadata to session export (#6611)

This commit is contained in:
Dillon Mulroy
2026-01-01 19:56:23 -05:00
committed by GitHub
parent 154c52c4d9
commit 05eee679a3
4 changed files with 497 additions and 93 deletions

View File

@@ -68,6 +68,7 @@ import { usePromptRef } from "../../context/prompt"
import { Filesystem } from "@/util/filesystem"
import { PermissionPrompt } from "./permission"
import { DialogExportOptions } from "../../ui/dialog-export-options"
import { formatTranscript } from "../../util/transcript"
addDefaultParsers(parsers.parsers)
@@ -134,6 +135,7 @@ export function Session() {
const [showTimestamps, setShowTimestamps] = createSignal(kv.get("timestamps", "hide") === "show")
const [usernameVisible, setUsernameVisible] = createSignal(kv.get("username_visible", true))
const [showDetails, setShowDetails] = createSignal(kv.get("tool_details_visibility", true))
const [showAssistantMetadata, setShowAssistantMetadata] = createSignal(kv.get("assistant_metadata_visibility", true))
const [showScrollbar, setShowScrollbar] = createSignal(kv.get("scrollbar_visible", false))
const [diffWrapMode, setDiffWrapMode] = createSignal<"word" | "none">("word")
const [animationsEnabled, setAnimationsEnabled] = createSignal(kv.get("animations_enabled", true))
@@ -712,47 +714,17 @@ export function Session() {
category: "Session",
onSelect: async (dialog) => {
try {
// Format session transcript as markdown
const sessionData = session()
const sessionMessages = messages()
let transcript = `# ${sessionData.title}\n\n`
transcript += `**Session ID:** ${sessionData.id}\n`
transcript += `**Created:** ${new Date(sessionData.time.created).toLocaleString()}\n`
transcript += `**Updated:** ${new Date(sessionData.time.updated).toLocaleString()}\n\n`
transcript += `---\n\n`
for (const msg of sessionMessages) {
const parts = sync.data.part[msg.id] ?? []
const role = msg.role === "user" ? "User" : "Assistant"
transcript += `## ${role}\n\n`
for (const part of parts) {
if (part.type === "text" && !part.synthetic) {
transcript += `${part.text}\n\n`
} else if (part.type === "reasoning") {
if (showThinking()) {
transcript += `_Thinking:_\n\n${part.text}\n\n`
}
} else if (part.type === "tool") {
transcript += `\`\`\`\nTool: ${part.tool}\n`
if (showDetails() && part.state.input) {
transcript += `\n**Input:**\n\`\`\`json\n${JSON.stringify(part.state.input, null, 2)}\n\`\`\``
}
if (showDetails() && part.state.status === "completed" && part.state.output) {
transcript += `\n**Output:**\n\`\`\`\n${part.state.output}\n\`\`\``
}
if (showDetails() && part.state.status === "error" && part.state.error) {
transcript += `\n**Error:**\n\`\`\`\n${part.state.error}\n\`\`\``
}
transcript += `\n\`\`\`\n\n`
}
}
transcript += `---\n\n`
}
// Copy to clipboard
const transcript = formatTranscript(
sessionData,
sessionMessages.map((msg) => ({ info: msg, parts: sync.data.part[msg.id] ?? [] })),
{
thinking: showThinking(),
toolDetails: showDetails(),
assistantMetadata: showAssistantMetadata(),
},
)
await Clipboard.copy(transcript)
toast.show({ message: "Session transcript copied to clipboard!", variant: "success" })
} catch (error) {
@@ -762,75 +734,56 @@ export function Session() {
},
},
{
title: "Export session transcript to file",
title: "Export session transcript",
value: "session.export",
keybind: "session_export",
category: "Session",
onSelect: async (dialog) => {
try {
// Format session transcript as markdown
const sessionData = session()
const sessionMessages = messages()
const defaultFilename = `session-${sessionData.id.slice(0, 8)}.md`
const options = await DialogExportOptions.show(dialog, defaultFilename, showThinking(), showDetails())
const options = await DialogExportOptions.show(
dialog,
defaultFilename,
showThinking(),
showDetails(),
showAssistantMetadata(),
false,
)
if (options === null) return
const { filename: customFilename, thinking: includeThinking, toolDetails: includeToolDetails } = options
const transcript = formatTranscript(
sessionData,
sessionMessages.map((msg) => ({ info: msg, parts: sync.data.part[msg.id] ?? [] })),
{
thinking: options.thinking,
toolDetails: options.toolDetails,
assistantMetadata: options.assistantMetadata,
},
)
let transcript = `# ${sessionData.title}\n\n`
transcript += `**Session ID:** ${sessionData.id}\n`
transcript += `**Created:** ${new Date(sessionData.time.created).toLocaleString()}\n`
transcript += `**Updated:** ${new Date(sessionData.time.updated).toLocaleString()}\n\n`
transcript += `---\n\n`
if (options.openWithoutSaving) {
// Just open in editor without saving
await Editor.open({ value: transcript, renderer })
} else {
const exportDir = process.cwd()
const filename = options.filename.trim()
const filepath = path.join(exportDir, filename)
for (const msg of sessionMessages) {
const parts = sync.data.part[msg.id] ?? []
const role = msg.role === "user" ? "User" : "Assistant"
transcript += `## ${role}\n\n`
await Bun.write(filepath, transcript)
for (const part of parts) {
if (part.type === "text" && !part.synthetic) {
transcript += `${part.text}\n\n`
} else if (part.type === "reasoning") {
if (includeThinking) {
transcript += `_Thinking:_\n\n${part.text}\n\n`
}
} else if (part.type === "tool") {
transcript += `\`\`\`\nTool: ${part.tool}\n`
if (includeToolDetails && part.state.input) {
transcript += `\n**Input:**\n\`\`\`json\n${JSON.stringify(part.state.input, null, 2)}\n\`\`\``
}
if (includeToolDetails && part.state.status === "completed" && part.state.output) {
transcript += `\n**Output:**\n\`\`\`\n${part.state.output}\n\`\`\``
}
if (includeToolDetails && part.state.status === "error" && part.state.error) {
transcript += `\n**Error:**\n\`\`\`\n${part.state.error}\n\`\`\``
}
transcript += `\n\`\`\`\n\n`
}
// Open with EDITOR if available
const result = await Editor.open({ value: transcript, renderer })
if (result !== undefined) {
await Bun.write(filepath, result)
}
transcript += `---\n\n`
toast.show({ message: `Session exported to ${filename}`, variant: "success" })
}
// Save to file in current working directory
const exportDir = process.cwd()
const filename = customFilename.trim()
const filepath = path.join(exportDir, filename)
await Bun.write(filepath, transcript)
// Open with EDITOR if available
const result = await Editor.open({ value: transcript, renderer })
if (result !== undefined) {
// User edited the file, save the changes
await Bun.write(filepath, result)
}
toast.show({ message: `Session exported to ${filename}`, variant: "success" })
} catch (error) {
toast.show({ message: "Failed to export session", variant: "error" })
}