Download packages/app/src/utils/session-message.ts from SaylorTwift/opencode: direct link, hf CLI and curl.
- Browser
- Download file 12 kB
-
https://huggingface.co/SaylorTwift/opencode/resolve/main/packages/app/src/utils/session-message.ts
- Command line
-
hf download hf://SaylorTwift/opencode/packages/app/src/utils/session-message.ts
-
curl -L -o session-message.ts https://huggingface.co/SaylorTwift/opencode/resolve/main/packages/app/src/utils/session-message.ts
12 kB
| import type { | |
| SessionMessageAssistant, | |
| SessionMessageAssistantTool, | |
| SessionMessageInfo, | |
| SessionMessageShell, | |
| SessionMessageUser, | |
| } from "@opencode-ai/client/promise" | |
| import type { AssistantMessage, FilePart, Message, Part, ToolPart, UserMessage } from "@opencode-ai/sdk/v2" | |
| import { Option, Schema } from "effect" | |
| const emptyTokens = { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } } | |
| const emptyModel: { id: string; providerID: string; variant?: string } = { id: "", providerID: "" } | |
| const decodeToolInput = Schema.decodeUnknownOption(Schema.UnknownFromJsonString) | |
| export function compareMessages(a: Pick<Message, "id" | "time">, b: Pick<Message, "id" | "time">) { | |
| const left = messageKey(a) | |
| const right = messageKey(b) | |
| return left < right ? -1 : left > right ? 1 : 0 | |
| } | |
| export const messageKey = (message: Pick<Message, "id" | "time">) => message.time.created + message.id | |
| function record(value: unknown): value is Record<string, unknown> { | |
| return !!value && typeof value === "object" && !Array.isArray(value) | |
| } | |
| function normalizeToolInput(name: string, input: Record<string, unknown>) { | |
| if (!["edit", "write"].includes(name) || typeof input.path !== "string" || typeof input.filePath === "string") | |
| return input | |
| return { ...input, filePath: input.path } | |
| } | |
| function normalizeToolMetadata(name: string, metadata: Record<string, unknown>) { | |
| if (name !== "edit" || !Array.isArray(metadata.files)) return metadata | |
| const file = metadata.files.find(record) | |
| if (!file || typeof file.file !== "string") return metadata | |
| return { | |
| ...metadata, | |
| filediff: { | |
| file: file.file, | |
| patch: typeof file.patch === "string" ? file.patch : undefined, | |
| additions: typeof file.additions === "number" ? file.additions : 0, | |
| deletions: typeof file.deletions === "number" ? file.deletions : 0, | |
| }, | |
| } | |
| } | |
| export function normalizeSessionMessages(sessionID: string, source: readonly SessionMessageInfo[]) { | |
| const messages: Message[] = [] | |
| const parts = new Map<string, Part[]>() | |
| let agent = "" | |
| let model = emptyModel | |
| let parentID: string | undefined | |
| source.forEach((message) => { | |
| if (message.type === "agent-switched") { | |
| agent = message.agent | |
| return | |
| } | |
| if (message.type === "model-switched") { | |
| model = message.model | |
| return | |
| } | |
| if (message.type === "user") { | |
| parentID = message.id | |
| messages.push(userMessage(sessionID, message, agent, model)) | |
| parts.set(message.id, userParts(sessionID, message)) | |
| return | |
| } | |
| if (message.type === "synthetic" && message.description?.trim()) { | |
| parentID = message.id | |
| messages.push({ | |
| id: message.id, | |
| sessionID, | |
| role: "user", | |
| time: message.time, | |
| agent, | |
| model: { providerID: model.providerID, modelID: model.id, variant: model.variant }, | |
| }) | |
| parts.set(message.id, [textPart(sessionID, message.id, 0, message.description, true)]) | |
| return | |
| } | |
| if (message.type === "shell") { | |
| messages.push(...shellMessages(sessionID, message, agent, model)) | |
| parts.set(message.id, [textPart(sessionID, message.id, 0, message.command)]) | |
| parts.set(`${message.id}:assistant`, [shellPart(sessionID, message)]) | |
| parentID = undefined | |
| return | |
| } | |
| if (message.type === "assistant") { | |
| agent = message.agent | |
| model = message.model | |
| if (!parentID) return | |
| const parent = messages.findLast((item) => item.id === parentID) | |
| if (parent?.role === "user") { | |
| parent.agent = message.agent | |
| parent.model = { | |
| providerID: message.model.providerID, | |
| modelID: message.model.id, | |
| variant: message.model.variant, | |
| } | |
| } | |
| messages.push(assistantMessage(sessionID, parentID, message)) | |
| parts.set(message.id, assistantParts(sessionID, message)) | |
| return | |
| } | |
| if (message.type !== "compaction" || !parentID) return | |
| parts.set(parentID, [ | |
| ...(parts.get(parentID) ?? []), | |
| { | |
| id: `${message.id}:compaction`, | |
| sessionID, | |
| messageID: parentID, | |
| type: "compaction", | |
| auto: message.reason === "auto", | |
| }, | |
| ]) | |
| }) | |
| return { messages, parts } | |
| } | |
| function shellMessages( | |
| sessionID: string, | |
| message: SessionMessageShell, | |
| agent: string, | |
| model: { id: string; providerID: string; variant?: string }, | |
| ): [UserMessage, AssistantMessage] { | |
| return [ | |
| { | |
| id: message.id, | |
| sessionID, | |
| role: "user", | |
| time: { created: message.time.created }, | |
| agent, | |
| model: { providerID: model.providerID, modelID: model.id, variant: model.variant }, | |
| }, | |
| { | |
| id: `${message.id}:assistant`, | |
| sessionID, | |
| role: "assistant", | |
| time: message.time, | |
| parentID: message.id, | |
| modelID: model.id, | |
| providerID: model.providerID, | |
| variant: model.variant, | |
| mode: agent, | |
| agent, | |
| path: { cwd: "", root: "" }, | |
| cost: 0, | |
| tokens: emptyTokens, | |
| }, | |
| ] | |
| } | |
| function shellPart(sessionID: string, message: SessionMessageShell): ToolPart { | |
| const input = { command: message.command } | |
| const start = message.time.created | |
| const state: ToolPart["state"] = | |
| message.status === "running" | |
| ? { status: "running", input, time: { start } } | |
| : { | |
| status: "completed", | |
| input, | |
| output: message.output?.output ?? "", | |
| title: "Shell", | |
| metadata: { | |
| status: message.status, | |
| exit: message.exit, | |
| truncated: message.output?.truncated, | |
| }, | |
| time: { start, end: message.time.completed ?? start }, | |
| } | |
| return { | |
| id: `${message.id}:tool`, | |
| sessionID, | |
| messageID: `${message.id}:assistant`, | |
| type: "tool", | |
| callID: message.shellID, | |
| tool: "bash", | |
| state, | |
| } | |
| } | |
| export function sessionMessagePartID(messageID: string, type: "text" | "reasoning", ordinal: number) { | |
| return `${messageID}:${type}:${ordinal}` | |
| } | |
| function userMessage( | |
| sessionID: string, | |
| message: SessionMessageUser, | |
| agent: string, | |
| model: { id: string; providerID: string; variant?: string }, | |
| ): UserMessage { | |
| return { | |
| id: message.id, | |
| sessionID, | |
| role: "user", | |
| time: message.time, | |
| agent, | |
| model: { providerID: model.providerID, modelID: model.id, variant: model.variant }, | |
| } | |
| } | |
| function userParts(sessionID: string, message: SessionMessageUser): Part[] { | |
| return [ | |
| textPart(sessionID, message.id, 0, message.text), | |
| ...(message.files ?? []).map( | |
| (file, index): FilePart => ({ | |
| id: `${message.id}:file:${index}`, | |
| sessionID, | |
| messageID: message.id, | |
| type: "file", | |
| mime: file.mime, | |
| filename: file.name, | |
| url: file.source.type === "uri" ? file.source.uri : `data:${file.mime};base64,${file.data}`, | |
| source: file.mention | |
| ? { | |
| type: "file", | |
| text: { value: file.mention.text, start: file.mention.start, end: file.mention.end }, | |
| path: file.mention.text.startsWith("@") ? file.mention.text.slice(1) : (file.name ?? file.mention.text), | |
| } | |
| : undefined, | |
| }), | |
| ), | |
| ...(message.agents ?? []).map( | |
| (item, index): Part => ({ | |
| id: `${message.id}:agent:${index}`, | |
| sessionID, | |
| messageID: message.id, | |
| type: "agent", | |
| name: item.name, | |
| source: item.mention | |
| ? { value: item.mention.text, start: item.mention.start, end: item.mention.end } | |
| : undefined, | |
| }), | |
| ), | |
| ] | |
| } | |
| function assistantMessage(sessionID: string, parentID: string, message: SessionMessageAssistant): AssistantMessage { | |
| const error = message.error | |
| ? message.error.type.toLowerCase().includes("abort") || message.error.type.toLowerCase().includes("interrupt") | |
| ? { name: "MessageAbortedError" as const, data: { message: message.error.message } } | |
| : { name: "UnknownError" as const, data: { message: message.error.message } } | |
| : undefined | |
| return { | |
| id: message.id, | |
| sessionID, | |
| role: "assistant", | |
| time: message.time, | |
| error, | |
| parentID, | |
| modelID: message.model.id, | |
| providerID: message.model.providerID, | |
| variant: message.model.variant, | |
| mode: message.agent, | |
| agent: message.agent, | |
| path: { cwd: "", root: "" }, | |
| cost: message.cost ?? 0, | |
| tokens: message.tokens ?? emptyTokens, | |
| finish: message.finish, | |
| } | |
| } | |
| function assistantParts(sessionID: string, message: SessionMessageAssistant): Part[] { | |
| const ordinals = { text: 0, reasoning: 0 } | |
| return message.content.flatMap((content): Part[] => { | |
| if (content.type === "text") { | |
| const part = textPart(sessionID, message.id, ordinals.text++, content.text) | |
| return content.text.trim() ? [part] : [] | |
| } | |
| if (content.type === "reasoning") { | |
| const part: Part = { | |
| id: sessionMessagePartID(message.id, "reasoning", ordinals.reasoning++), | |
| sessionID, | |
| messageID: message.id, | |
| type: "reasoning", | |
| text: content.text, | |
| metadata: content.state, | |
| time: { | |
| start: content.time?.created ?? message.time.created, | |
| end: content.time?.completed, | |
| }, | |
| } | |
| return content.text.trim() ? [part] : [] | |
| } | |
| return [toolPart(sessionID, message.id, content)] | |
| }) | |
| } | |
| function textPart(sessionID: string, messageID: string, ordinal: number, text: string, synthetic?: boolean): Part { | |
| return { | |
| id: sessionMessagePartID(messageID, "text", ordinal), | |
| sessionID, | |
| messageID, | |
| type: "text", | |
| text, | |
| synthetic, | |
| } | |
| } | |
| function toolPart(sessionID: string, messageID: string, tool: SessionMessageAssistantTool): ToolPart { | |
| const start = tool.time.ran ?? tool.time.created | |
| const state = (() => { | |
| if (tool.state.status === "streaming") { | |
| const value = Option.getOrUndefined(decodeToolInput(tool.state.input)) | |
| const input = normalizeToolInput(tool.name, record(value) ? value : {}) | |
| return { status: "pending" as const, input, raw: tool.state.input } | |
| } | |
| if (tool.state.status === "running") { | |
| return { | |
| status: "running" as const, | |
| input: normalizeToolInput(tool.name, tool.state.input), | |
| // metadata: normalizeToolMetadata(tool.name, tool.state.structured), | |
| metadata: normalizeToolMetadata(tool.name, tool.state.metadata ?? {}), | |
| time: { start }, | |
| } | |
| } | |
| if (tool.state.status === "error") { | |
| return { | |
| status: "error" as const, | |
| input: normalizeToolInput(tool.name, tool.state.input), | |
| error: tool.state.error.message, | |
| // metadata: normalizeToolMetadata(tool.name, tool.state.structured), | |
| metadata: normalizeToolMetadata(tool.name, tool.state.metadata ?? {}), | |
| time: { start, end: tool.time.completed ?? start }, | |
| } | |
| } | |
| const attachments = tool.state.content.flatMap((item, index): FilePart[] => | |
| item.type === "file" | |
| ? [ | |
| { | |
| id: `${tool.id}:file:${index}`, | |
| sessionID, | |
| messageID, | |
| type: "file", | |
| mime: item.mime, | |
| filename: item.name, | |
| url: item.uri, | |
| }, | |
| ] | |
| : [], | |
| ) | |
| return { | |
| status: "completed" as const, | |
| input: normalizeToolInput(tool.name, tool.state.input), | |
| output: tool.state.content.flatMap((item) => (item.type === "text" ? [item.text] : [])).join("\n"), | |
| title: tool.name, | |
| // metadata: normalizeToolMetadata(tool.name, tool.state.structured), | |
| metadata: normalizeToolMetadata(tool.name, tool.state.metadata ?? {}), | |
| time: { start, end: tool.time.completed ?? start }, | |
| attachments: attachments.length ? attachments : undefined, | |
| } | |
| })() | |
| return { | |
| id: tool.id, | |
| sessionID, | |
| messageID, | |
| type: "tool", | |
| callID: tool.id, | |
| tool: tool.name, | |
| state, | |
| metadata: { providerState: tool.providerState, providerResultState: tool.providerResultState }, | |
| } | |
| } | |