/** * Durable pi-ai replay metadata and assistant-history reconstruction. * * Harness content remains the durable source for text and tool calls. This * module stores only the provider-native metadata needed to reconstruct a * pi-ai assistant message on a later request. * * @module dsh-llm-pi-ai/replay */ import { LlmError } from '@deepseek-ai/dsh-llm' import type { Message, ModelMessageSource } from '@deepseek-ai/dsh-llm' import type { Api, AssistantMessage, Usage as PiUsage } from '@earendil-works/pi-ai' type PiAiReplayBlock = | { type: 'text'; textSignature?: string } | { type: 'reasoning'; thinkingSignature?: string; redacted?: boolean } | { type: 'tool-call'; thoughtSignature?: string } /** Versioned adapter-private projection required to replay a pi-ai response. */ export interface PiAiReplayState { kind: 'pi-ai' version: 1 api: Api provider: string model: string responseModel?: string responseId?: string stopReason: AssistantMessage['stopReason'] blocks: PiAiReplayBlock[] } /** Parse tool-call argument JSON; tolerate model malformations with {}. */ function parseArguments(raw: string): Record { try { const parsed: unknown = JSON.parse(raw) if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) { return parsed as Record } } catch { // fall through } return {} } /** Construct the zero usage value required by historical pi-ai messages. */ function emptyPiUsage(): PiUsage { return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, } } /** * Project a successful pi-ai response into the minimal durable replay state. * @param message - completed native pi-ai assistant response. * @returns the versioned lossless-JSON replay projection. */ export function toPiReplayState(message: AssistantMessage): PiAiReplayState { return { kind: 'pi-ai', version: 1, api: message.api, provider: message.provider, model: message.model, ...message.responseModel === undefined ? {} : { responseModel: message.responseModel }, ...message.responseId === undefined ? {} : { responseId: message.responseId }, stopReason: message.stopReason, blocks: message.content.map((block): PiAiReplayBlock => { switch (block.type) { case 'text': return { type: 'text', ...block.textSignature === undefined ? {} : { textSignature: block.textSignature }, } case 'thinking': return { type: 'reasoning', ...block.thinkingSignature === undefined ? {} : { thinkingSignature: block.thinkingSignature }, ...block.redacted === undefined ? {} : { redacted: block.redacted }, } case 'toolCall': return { type: 'tool-call', ...block.thoughtSignature === undefined ? {} : { thoughtSignature: block.thoughtSignature }, } } }), } } function invalidReplay(message: string): never { throw new LlmError(`invalid pi-ai replay state: ${message}`, 'INVALID_REPLAY_STATE') } /** Validate the adapter-private state before it reaches pi-ai. */ function readReplayState(value: unknown): PiAiReplayState { if (typeof value !== 'object' || value === null || Array.isArray(value)) return invalidReplay('expected an object') const state = value as Record if (state['kind'] !== 'pi-ai') return invalidReplay('unknown state kind') if (state['version'] !== 1) return invalidReplay(`unsupported version ${String(state['version'])}`) for (const key of ['api', 'provider', 'model'] as const) { if (typeof state[key] !== 'string' || state[key].length === 0) return invalidReplay(`${key} must be a non-empty string`) } if (!['stop', 'length', 'toolUse', 'error', 'aborted'].includes(String(state['stopReason']))) { return invalidReplay('unknown stopReason') } if (state['responseModel'] !== undefined && typeof state['responseModel'] !== 'string') return invalidReplay('responseModel must be a string') if (state['responseId'] !== undefined && typeof state['responseId'] !== 'string') return invalidReplay('responseId must be a string') if (!Array.isArray(state['blocks'])) return invalidReplay('blocks must be an array') for (const [index, value] of state['blocks'].entries()) { if (typeof value !== 'object' || value === null || Array.isArray(value)) return invalidReplay(`block ${index} must be an object`) const block = value as Record if (!['text', 'reasoning', 'tool-call'].includes(String(block['type']))) return invalidReplay(`block ${index} has an unknown type`) for (const signature of ['textSignature', 'thinkingSignature', 'thoughtSignature'] as const) { if (block[signature] !== undefined && typeof block[signature] !== 'string') return invalidReplay(`block ${index} ${signature} must be a string`) } if (block['redacted'] !== undefined && typeof block['redacted'] !== 'boolean') return invalidReplay(`block ${index} redacted must be boolean`) } return state as unknown as PiAiReplayState } /** Convert provider-neutral blocks without trusting them as same-model replay. */ function foreignAssistant(message: Message): AssistantMessage { const source = message.source.kind === 'model' ? message.source : undefined const content: AssistantMessage['content'] = [] for (const block of message.content) { switch (block.type) { case 'text': content.push({ type: 'text', text: block.text }); break case 'reasoning': content.push({ type: 'thinking', thinking: block.text }); break case 'tool-call': content.push({ type: 'toolCall', id: block.id, name: block.name, arguments: parseArguments(block.arguments), }); break default: // plugin-added block types are not representable in pi-ai. break } } return { role: 'assistant', content, // Deliberately never equals a catalog API: absent replay state is foreign // even if source names the same provider/model as this request. api: 'dsh-foreign', provider: source?.provider ?? 'dsh-foreign', model: source?.model ?? 'dsh-foreign', usage: emptyPiUsage(), stopReason: content.some(piece => piece.type === 'toolCall') ? 'toolUse' : 'stop', timestamp: 0, } } /** Recombine durable Harness content with validated pi-ai replay metadata. */ function replayedAssistant(message: Message, source: ModelMessageSource, rawState: unknown): AssistantMessage { const state = readReplayState(rawState) if (state.provider !== source.provider) return invalidReplay('provider does not match assistant source') if (state.model !== source.model) return invalidReplay('model does not match assistant source') if (state.blocks.length !== message.content.length) return invalidReplay('block count does not match assistant content') const content: AssistantMessage['content'] = message.content.map((block, index) => { const replay = state.blocks[index] if (replay === undefined || replay.type !== block.type) return invalidReplay(`block ${index} does not match assistant content`) switch (block.type) { case 'text': return { type: 'text', text: block.text, ...replay.type === 'text' && replay.textSignature !== undefined ? { textSignature: replay.textSignature } : {}, } case 'reasoning': return { type: 'thinking', thinking: block.text, ...replay.type === 'reasoning' && replay.thinkingSignature !== undefined ? { thinkingSignature: replay.thinkingSignature } : {}, ...replay.type === 'reasoning' && replay.redacted !== undefined ? { redacted: replay.redacted } : {}, } case 'tool-call': return { type: 'toolCall', id: block.id, name: block.name, arguments: parseArguments(block.arguments), ...replay.type === 'tool-call' && replay.thoughtSignature !== undefined ? { thoughtSignature: replay.thoughtSignature } : {}, } /* v8 ignore next -- readReplayState rejects unknown replay tags, so an equal plugin-added Harness tag cannot reach this switch */ default: return invalidReplay(`block ${index} has an unsupported Harness type`) } }) return { role: 'assistant', content, api: state.api, provider: state.provider, model: state.model, ...state.responseModel === undefined ? {} : { responseModel: state.responseModel }, ...state.responseId === undefined ? {} : { responseId: state.responseId }, usage: emptyPiUsage(), stopReason: state.stopReason, timestamp: 0, } } /** * Convert one durable Harness assistant message into pi-ai history. * @param message - assistant content with required source and optional adapter-owned replay metadata. * @returns a native pi-ai assistant message reconstructed from durable content. */ export function toPiAssistant(message: Message): AssistantMessage { const source = message.source return source.kind !== 'model' || source.replayState === undefined ? foreignAssistant(message) : replayedAssistant(message, source, source.replayState) }