import { toast } from "sonner" import { normalizeUnixTimestamp } from "@/features/chat/state" import { type AssistantMessageKind, type ContextUsage, updateChatStore, } from "@/store/chat" export interface PicoMessage { type: string id?: string session_id?: string timestamp?: number | string payload?: Record } function parseAssistantMessageKind( payload: Record, ): AssistantMessageKind { return payload.thought === true ? "thought" : "normal" } function hasAssistantKindPayload(payload: Record): boolean { return typeof payload.thought === "boolean" } function parseContextUsage( payload: Record, ): ContextUsage | undefined { const raw = payload.context_usage if (!raw || typeof raw !== "object") return undefined const obj = raw as Record const used = Number(obj.used_tokens) const total = Number(obj.total_tokens) if (!Number.isFinite(used) || !Number.isFinite(total) || total <= 0) return undefined return { used_tokens: used, total_tokens: total, compress_at_tokens: Number(obj.compress_at_tokens) || 0, used_percent: Number(obj.used_percent) || 0, } } export function handlePicoMessage( message: PicoMessage, expectedSessionId: string, ) { if (message.session_id && message.session_id !== expectedSessionId) { return } const payload = message.payload || {} switch (message.type) { case "message.create": { const content = (payload.content as string) || "" const messageId = (payload.message_id as string) || `pico-${Date.now()}` const kind = parseAssistantMessageKind(payload) const contextUsage = parseContextUsage(payload) const timestamp = message.timestamp !== undefined && Number.isFinite(Number(message.timestamp)) ? normalizeUnixTimestamp(Number(message.timestamp)) : Date.now() updateChatStore((prev) => ({ messages: [ ...prev.messages, { id: messageId, role: "assistant", content, kind, timestamp, }, ], isTyping: false, ...(contextUsage ? { contextUsage } : {}), })) break } case "message.update": { const content = (payload.content as string) || "" const messageId = payload.message_id as string const hasKind = hasAssistantKindPayload(payload) const kind = parseAssistantMessageKind(payload) if (!messageId) { break } updateChatStore((prev) => ({ messages: prev.messages.map((msg) => msg.id === messageId ? { ...msg, content, ...(hasKind ? { kind } : {}), } : msg, ), })) break } case "typing.start": updateChatStore({ isTyping: true }) break case "typing.stop": updateChatStore({ isTyping: false }) break case "error": { const requestId = typeof payload.request_id === "string" ? payload.request_id : "" const errorMessage = typeof payload.message === "string" ? payload.message : "" console.error("Pico error:", payload) if (errorMessage) { toast.error(errorMessage) } updateChatStore((prev) => ({ messages: requestId ? prev.messages.filter((msg) => msg.id !== requestId) : prev.messages, isTyping: false, })) break } case "pong": break default: console.log("Unknown pico message type:", message.type) } }