import { appendTranscriptMessage, publishTranscriptUpdate, resolveSessionTranscriptRuntimeReadTarget, resolveSessionTranscriptRuntimeTarget, type TranscriptMessageAppendOptions, type TranscriptMessageAppendResult, type TranscriptUpdatePayload, } from "../config/sessions/session-accessor.js"; import { runSessionTranscriptAppendTransaction } from "../config/sessions/transcript-append.js"; import { streamSessionTranscriptLines } from "../config/sessions/transcript-stream.js"; import { normalizeAgentId } from "../routing/session-key.js"; import { formatSessionTranscriptMemoryHitKey, parseSessionTranscriptMemoryHitKey, resolveSessionTranscriptMemoryHitKeyToSessionKeys, type ResolveSessionTranscriptMemoryHitKeyParams, type SessionTranscriptIdentity, type SessionTranscriptMemoryHitIdentity, type SessionTranscriptMemoryHitKey, type SessionTranscriptMemoryHitKeyParams, type SessionTranscriptReadParams, } from "./session-transcript-memory-hit.js"; export { formatSessionTranscriptMemoryHitKey, parseSessionTranscriptMemoryHitKey, resolveSessionTranscriptMemoryHitKeyToSessionKeys, }; export type { ResolveSessionTranscriptMemoryHitKeyParams, SessionTranscriptIdentity, SessionTranscriptMemoryHitIdentity, SessionTranscriptMemoryHitKey, SessionTranscriptMemoryHitKeyParams, SessionTranscriptReadParams, }; export type SessionTranscriptEvent = unknown; export type SessionTranscriptTargetParams = SessionTranscriptReadParams & { /** * @deprecated Prefer `{ agentId, sessionKey, sessionId }`. Pass this only * when adapting code that already receives an active transcript artifact and * needs each helper to operate on that same artifact. */ sessionFile?: string; }; export type SessionTranscriptTarget = SessionTranscriptIdentity & { targetKind: "active-session-file" | "runtime-session"; }; export type SessionTranscriptAppendMessageParams = SessionTranscriptTargetParams & TranscriptMessageAppendOptions; export type SessionTranscriptWriteLockParams = SessionTranscriptTargetParams & { config?: TranscriptMessageAppendOptions["config"]; }; export type SessionTranscriptWriteLockContext = { appendMessage: ( options: Omit, "config">, ) => Promise | undefined>; publishUpdate: (update?: TranscriptUpdatePayload) => Promise; readEvents: () => Promise; target: SessionTranscriptTarget; }; /** * Resolves the public identity for a transcript without returning its file path. */ export async function resolveSessionTranscriptIdentity( params: SessionTranscriptReadParams, ): Promise { const target = await resolveSessionTranscriptRuntimeReadTarget(params); const agentId = normalizeAgentId(target.agentId); return { agentId, memoryKey: formatSessionTranscriptMemoryHitKey({ agentId, sessionId: target.sessionId }), sessionId: target.sessionId, sessionKey: target.sessionKey, }; } /** * Resolves the public target for transcript operations without exposing the * current storage path as identity. */ export async function resolveSessionTranscriptTarget( params: SessionTranscriptTargetParams, ): Promise { const target = await resolveSessionTranscriptRuntimeReadTarget(params); return projectPublicTarget({ ...target, targetKind: params.sessionFile?.trim() ? "active-session-file" : "runtime-session", }); } /** * Reads transcript events by public session identity instead of file path. */ export async function readSessionTranscriptEvents( params: SessionTranscriptTargetParams, ): Promise { const target = await resolveSessionTranscriptRuntimeReadTarget(params); const events: SessionTranscriptEvent[] = []; for await (const line of streamSessionTranscriptLines(target.sessionFile)) { try { events.push(JSON.parse(line) as SessionTranscriptEvent); } catch { continue; } } return events; } /** * Appends a transcript message by scoped transcript target. */ export async function appendSessionTranscriptMessageByIdentity( params: SessionTranscriptAppendMessageParams, ): Promise | undefined> { return await appendTranscriptMessage(params, params); } /** * Publishes a transcript update by scoped transcript target. */ export async function publishSessionTranscriptUpdateByIdentity( params: SessionTranscriptTargetParams & { update?: TranscriptUpdatePayload }, ): Promise { const target = await resolveSessionTranscriptRuntimeTarget(params); await publishTranscriptUpdate( { ...params, sessionFile: target.sessionFile, }, { ...params.update, agentId: target.agentId, sessionKey: target.sessionKey, }, ); } /** * Runs transcript work under the write lock for the resolved scoped target. */ export async function withSessionTranscriptWriteLock( params: SessionTranscriptWriteLockParams, run: (context: SessionTranscriptWriteLockContext) => Promise | T, ): Promise { const storageTarget = await resolveSessionTranscriptRuntimeTarget(params); const target = projectPublicTarget({ ...storageTarget, targetKind: params.sessionFile?.trim() ? "active-session-file" : "runtime-session", }); const boundScope = { ...params, sessionFile: storageTarget.sessionFile, }; // Treat publishUpdate as a post-commit callback: future transactional stores // must not expose updates when the scoped write callback fails. const queuedUpdates: Array = []; const result = await runSessionTranscriptAppendTransaction( { config: params.config, transcriptPath: storageTarget.sessionFile, }, (transaction) => run({ target, readEvents: () => readSessionTranscriptEvents(boundScope), appendMessage: (options) => transaction.appendMessage({ ...options, sessionId: params.sessionId, }), publishUpdate: async (update) => { queuedUpdates.push(update ? { ...update } : undefined); }, }), ); for (const update of queuedUpdates) { await publishSessionTranscriptUpdateByIdentity({ ...boundScope, update, }); } return result; } function projectPublicTarget(target: { agentId: string; sessionId: string; sessionKey: string; targetKind: SessionTranscriptTarget["targetKind"]; }): SessionTranscriptTarget { const agentId = normalizeAgentId(target.agentId); return { agentId, memoryKey: formatSessionTranscriptMemoryHitKey({ agentId, sessionId: target.sessionId }), sessionId: target.sessionId, sessionKey: target.sessionKey, targetKind: target.targetKind, }; }