refactor(server): canonicalize service API (#31049)
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import type { Part } from "@opencode-ai/sdk/v2"
|
||||
import { Locale } from "../../util/locale"
|
||||
|
||||
export function relativeTime(timestamp: number): string {
|
||||
const diff = Date.now() - timestamp
|
||||
if (diff < 0) return "just now"
|
||||
const seconds = Math.floor(diff / 1000)
|
||||
if (seconds < 60) return "just now"
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
if (minutes < 60) return `${minutes}m ago`
|
||||
const hours = Math.floor(minutes / 60)
|
||||
if (hours < 24) return `${hours}h ago`
|
||||
const days = Math.floor(hours / 24)
|
||||
if (days < 7) return `${days}d ago`
|
||||
const d = new Date(timestamp)
|
||||
return d.toLocaleDateString(undefined, { month: "short", day: "numeric" })
|
||||
}
|
||||
|
||||
export function extractMessageText(parts: readonly Part[], maxLength: number): string {
|
||||
const joined = collectTextParts(parts).join(" ").replace(/\s+/g, " ").trim()
|
||||
return Locale.truncate(joined, maxLength)
|
||||
}
|
||||
|
||||
export function extractMessageMarkdown(parts: readonly Part[], maxLines: number, maxChars: number): string {
|
||||
const joined = collectTextParts(parts).join("\n\n").trim()
|
||||
if (!joined) return joined
|
||||
|
||||
let truncated = joined
|
||||
const lines = truncated.split("\n")
|
||||
if (lines.length > maxLines) {
|
||||
truncated = lines.slice(0, maxLines).join("\n")
|
||||
}
|
||||
if (truncated.length > maxChars) {
|
||||
truncated = truncated.slice(0, maxChars).trimEnd()
|
||||
}
|
||||
if (truncated.length === joined.length) return joined
|
||||
// Close any unterminated fenced code block so the renderer doesn't keep
|
||||
// the rest of the panel in "code mode".
|
||||
const fences = (truncated.match(/^```/gm) ?? []).length
|
||||
if (fences % 2 === 1) truncated += "\n```"
|
||||
return truncated + "\n\n…"
|
||||
}
|
||||
|
||||
function collectTextParts(parts: readonly Part[]): string[] {
|
||||
const chunks: string[] = []
|
||||
for (const part of parts) {
|
||||
if (part.type !== "text") continue
|
||||
const p = part as Part & { type: "text"; text: string; synthetic?: boolean; ignored?: boolean }
|
||||
if (p.synthetic || p.ignored) continue
|
||||
if (!p.text) continue
|
||||
chunks.push(p.text)
|
||||
}
|
||||
return chunks
|
||||
}
|
||||
Reference in New Issue
Block a user