mirror of
https://github.com/stablyai/orca.git
synced 2026-10-17 00:03:08 +00:00
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Orca <help@stably.ai> Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
67 lines
2.6 KiB
TypeScript
67 lines
2.6 KiB
TypeScript
import { createReadStream } from 'fs'
|
|
import { createInterface } from 'readline'
|
|
import type { AgentType, NativeChatMessage } from '../../shared/native-chat-types'
|
|
import { errorMessage } from '../ai-vault/session-scanner-values'
|
|
import { resolveSessionFilePath, type ResolveSessionFileOptions } from './session-file-resolver'
|
|
import { decodeClaudeTranscriptLine, decodeCodexTranscriptLine } from './transcript-line-decoders'
|
|
|
|
export type ReadTranscriptResult = { messages: NativeChatMessage[] } | { error: string }
|
|
|
|
export type ReadTranscriptOptions = ResolveSessionFileOptions & {
|
|
/** Resolve directly to this file, skipping path discovery (used by tests). */
|
|
filePath?: string
|
|
}
|
|
|
|
/**
|
|
* Read the ENTIRE Claude/Codex JSONL transcript for an agent + session id into
|
|
* the NativeChatMessage model. Unlike the AI-Vault preview scan, this applies
|
|
* NO message cap. Unknown record types are skipped rather than throwing, so a
|
|
* single malformed/unrecognized line cannot fail the whole read. The per-line
|
|
* record→message mapping is shared with the live tailer (transcript-watch.ts)
|
|
* via transcript-line-decoders.ts.
|
|
*/
|
|
export async function readNativeChatTranscript(
|
|
agent: AgentType,
|
|
sessionId: string,
|
|
options: ReadTranscriptOptions = {}
|
|
): Promise<ReadTranscriptResult> {
|
|
const filePath = options.filePath ?? (await resolveSessionFilePath(agent, sessionId, options))
|
|
if (!filePath) {
|
|
return { error: `No transcript found for ${agent} session ${sessionId}` }
|
|
}
|
|
try {
|
|
if (agent === 'claude') {
|
|
return { messages: await readTranscript(filePath, decodeClaudeTranscriptLine) }
|
|
}
|
|
if (agent === 'codex') {
|
|
return { messages: await readTranscript(filePath, decodeCodexTranscriptLine) }
|
|
}
|
|
return { error: `Unsupported agent for native chat transcript: ${agent}` }
|
|
} catch (err) {
|
|
return { error: errorMessage(err) }
|
|
}
|
|
}
|
|
|
|
async function readTranscript(
|
|
filePath: string,
|
|
decode: (line: string, fallbackId: string) => NativeChatMessage | null
|
|
): Promise<NativeChatMessage[]> {
|
|
const reader = createInterface({
|
|
input: createReadStream(filePath, { encoding: 'utf-8' }),
|
|
crlfDelay: Infinity
|
|
})
|
|
const messages: NativeChatMessage[] = []
|
|
let index = 0
|
|
for await (const line of reader) {
|
|
// Why: fallback id embeds start offset 0 so it matches the live tailer's id
|
|
// for the same record (the tailer's first drain reads from offset 0 too).
|
|
// Records that re-emit then collapse by id in the assembler — no dup, no drop.
|
|
const message = decode(line, `${filePath}:0:${index}`)
|
|
if (message) {
|
|
messages.push(message)
|
|
}
|
|
index++
|
|
}
|
|
return messages
|
|
}
|