@kaifronsdal/transcript-viewer
Version:
A web-based viewer for AI conversation transcripts with rollback support
520 lines (517 loc) • 39.4 kB
JavaScript
import { promises } from 'fs';
import path from 'path';
import { watch } from 'chokidar';
import { EventEmitter } from 'events';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import { c as createTranscriptDisplay } from './transcript-utils-BY7i01oF.js';
import { a as DEFAULT_TRANSCRIPT_DIR } from './constants-C6XQO3qi.js';
const $defs = /* @__PURE__ */ JSON.parse('{"AddMessage":{"description":"Edit that adds a message to the transcript.","properties":{"operation":{"const":"add","default":"add","title":"Operation","type":"string"},"message":{"discriminator":{"mapping":{"assistant":"#/$defs/ChatMessageAssistant","system":"#/$defs/ChatMessageSystem","tool":"#/$defs/ChatMessageTool","user":"#/$defs/ChatMessageUser"},"propertyName":"role"},"oneOf":[{"$ref":"#/$defs/ChatMessageSystem"},{"$ref":"#/$defs/ChatMessageUser"},{"$ref":"#/$defs/ChatMessageAssistant"},{"$ref":"#/$defs/ChatMessageTool"}],"title":"Message"}},"required":["message"],"title":"AddMessage","type":"object"},"ChatMessageAssistant":{"description":"Assistant chat message.","properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Id"},"content":{"anyOf":[{"type":"string"},{"items":{"anyOf":[{"$ref":"#/$defs/ContentText"},{"$ref":"#/$defs/ContentReasoning"},{"$ref":"#/$defs/ContentImage"},{"$ref":"#/$defs/ContentAudio"},{"$ref":"#/$defs/ContentVideo"},{"$ref":"#/$defs/ContentData"},{"$ref":"#/$defs/ContentToolUse"},{"$ref":"#/$defs/ContentDocument"}]},"type":"array"}],"title":"Content"},"source":{"anyOf":[{"enum":["input","generate"],"type":"string"},{"type":"null"}],"default":null,"title":"Source"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"default":null,"title":"Metadata"},"internal":{"anyOf":[{"$ref":"#/$defs/JsonValue"},{"type":"null"}],"default":null},"role":{"const":"assistant","default":"assistant","title":"Role","type":"string"},"tool_calls":{"anyOf":[{"items":{"$ref":"#/$defs/ToolCall"},"type":"array"},{"type":"null"}],"default":null,"title":"Tool Calls"},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Model"}},"required":["content"],"title":"ChatMessageAssistant","type":"object"},"ChatMessageSystem":{"description":"System chat message.","properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Id"},"content":{"anyOf":[{"type":"string"},{"items":{"anyOf":[{"$ref":"#/$defs/ContentText"},{"$ref":"#/$defs/ContentReasoning"},{"$ref":"#/$defs/ContentImage"},{"$ref":"#/$defs/ContentAudio"},{"$ref":"#/$defs/ContentVideo"},{"$ref":"#/$defs/ContentData"},{"$ref":"#/$defs/ContentToolUse"},{"$ref":"#/$defs/ContentDocument"}]},"type":"array"}],"title":"Content"},"source":{"anyOf":[{"enum":["input","generate"],"type":"string"},{"type":"null"}],"default":null,"title":"Source"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"default":null,"title":"Metadata"},"internal":{"anyOf":[{"$ref":"#/$defs/JsonValue"},{"type":"null"}],"default":null},"role":{"const":"system","default":"system","title":"Role","type":"string"}},"required":["content"],"title":"ChatMessageSystem","type":"object"},"ChatMessageTool":{"description":"Tool chat message.","properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Id"},"content":{"anyOf":[{"type":"string"},{"items":{"anyOf":[{"$ref":"#/$defs/ContentText"},{"$ref":"#/$defs/ContentReasoning"},{"$ref":"#/$defs/ContentImage"},{"$ref":"#/$defs/ContentAudio"},{"$ref":"#/$defs/ContentVideo"},{"$ref":"#/$defs/ContentData"},{"$ref":"#/$defs/ContentToolUse"},{"$ref":"#/$defs/ContentDocument"}]},"type":"array"}],"title":"Content"},"source":{"anyOf":[{"enum":["input","generate"],"type":"string"},{"type":"null"}],"default":null,"title":"Source"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"default":null,"title":"Metadata"},"internal":{"anyOf":[{"$ref":"#/$defs/JsonValue"},{"type":"null"}],"default":null},"role":{"const":"tool","default":"tool","title":"Role","type":"string"},"tool_call_id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Tool Call Id"},"function":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Function"},"error":{"anyOf":[{"$ref":"#/$defs/ToolCallError"},{"type":"null"}],"default":null}},"required":["content"],"title":"ChatMessageTool","type":"object"},"ChatMessageUser":{"description":"User chat message.","properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Id"},"content":{"anyOf":[{"type":"string"},{"items":{"anyOf":[{"$ref":"#/$defs/ContentText"},{"$ref":"#/$defs/ContentReasoning"},{"$ref":"#/$defs/ContentImage"},{"$ref":"#/$defs/ContentAudio"},{"$ref":"#/$defs/ContentVideo"},{"$ref":"#/$defs/ContentData"},{"$ref":"#/$defs/ContentToolUse"},{"$ref":"#/$defs/ContentDocument"}]},"type":"array"}],"title":"Content"},"source":{"anyOf":[{"enum":["input","generate"],"type":"string"},{"type":"null"}],"default":null,"title":"Source"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"default":null,"title":"Metadata"},"internal":{"anyOf":[{"$ref":"#/$defs/JsonValue"},{"type":"null"}],"default":null},"role":{"const":"user","default":"user","title":"Role","type":"string"},"tool_call_id":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"default":null,"title":"Tool Call Id"}},"required":["content"],"title":"ChatMessageUser","type":"object"},"ContentAudio":{"description":"Audio content.","properties":{"internal":{"anyOf":[{"$ref":"#/$defs/JsonValue"},{"type":"null"}],"default":null},"type":{"const":"audio","default":"audio","title":"Type","type":"string"},"audio":{"title":"Audio","type":"string"},"format":{"enum":["wav","mp3"],"title":"Format","type":"string"}},"required":["audio","format"],"title":"ContentAudio","type":"object"},"ContentCitation":{"description":"A generic content citation.","properties":{"cited_text":{"anyOf":[{"type":"string"},{"additionalItems":false,"items":[{"type":"integer"},{"type":"integer"}],"maxItems":2,"minItems":2,"type":"array"},{"type":"null"}],"default":null,"title":"Cited Text"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Title"},"internal":{"anyOf":[{"additionalProperties":{"$ref":"#/$defs/JsonValue"},"type":"object"},{"type":"null"}],"default":null,"title":"Internal"},"type":{"const":"content","default":"content","title":"Type","type":"string"}},"title":"ContentCitation","type":"object"},"ContentData":{"description":"Model internal.","properties":{"internal":{"anyOf":[{"$ref":"#/$defs/JsonValue"},{"type":"null"}],"default":null},"type":{"const":"data","default":"data","title":"Type","type":"string"},"data":{"additionalProperties":{"$ref":"#/$defs/JsonValue"},"title":"Data","type":"object"}},"required":["data"],"title":"ContentData","type":"object"},"ContentDocument":{"description":"Document content (e.g. a PDF).","properties":{"internal":{"anyOf":[{"$ref":"#/$defs/JsonValue"},{"type":"null"}],"default":null},"type":{"const":"document","default":"document","title":"Type","type":"string"},"document":{"title":"Document","type":"string"},"filename":{"title":"Filename","type":"string"},"mime_type":{"title":"Mime Type","type":"string"}},"required":["document"],"title":"ContentDocument","type":"object"},"ContentImage":{"description":"Image content.","properties":{"internal":{"anyOf":[{"$ref":"#/$defs/JsonValue"},{"type":"null"}],"default":null},"type":{"const":"image","default":"image","title":"Type","type":"string"},"image":{"title":"Image","type":"string"},"detail":{"default":"auto","enum":["auto","low","high"],"title":"Detail","type":"string"}},"required":["image"],"title":"ContentImage","type":"object"},"ContentReasoning":{"description":"Reasoning content.\\n\\nSee the specification for [thinking blocks](https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#understanding-thinking-blocks) for Claude models.","properties":{"internal":{"anyOf":[{"$ref":"#/$defs/JsonValue"},{"type":"null"}],"default":null},"type":{"const":"reasoning","default":"reasoning","title":"Type","type":"string"},"reasoning":{"title":"Reasoning","type":"string"},"signature":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Signature"},"redacted":{"default":false,"title":"Redacted","type":"boolean"}},"required":["reasoning"],"title":"ContentReasoning","type":"object"},"ContentText":{"description":"Text content.","properties":{"internal":{"anyOf":[{"$ref":"#/$defs/JsonValue"},{"type":"null"}],"default":null},"type":{"const":"text","default":"text","title":"Type","type":"string"},"text":{"title":"Text","type":"string"},"refusal":{"anyOf":[{"type":"boolean"},{"type":"null"}],"default":null,"title":"Refusal"},"citations":{"anyOf":[{"items":{"discriminator":{"mapping":{"content":"#/$defs/ContentCitation","document":"#/$defs/DocumentCitation","url":"#/$defs/UrlCitation"},"propertyName":"type"},"oneOf":[{"$ref":"#/$defs/ContentCitation"},{"$ref":"#/$defs/DocumentCitation"},{"$ref":"#/$defs/UrlCitation"}]},"type":"array"},{"type":"null"}],"default":null,"title":"Citations"}},"required":["text"],"title":"ContentText","type":"object"},"ContentToolUse":{"description":"Server side tool use.","properties":{"internal":{"anyOf":[{"$ref":"#/$defs/JsonValue"},{"type":"null"}],"default":null},"type":{"const":"tool_use","default":"tool_use","title":"Type","type":"string"},"tool_type":{"title":"Tool Type","type":"string"},"id":{"title":"Id","type":"string"},"name":{"title":"Name","type":"string"},"context":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Context"},"arguments":{"$ref":"#/$defs/JsonValue"},"result":{"$ref":"#/$defs/JsonValue"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Error"}},"required":["tool_type","id","name","arguments","result"],"title":"ContentToolUse","type":"object"},"ContentVideo":{"description":"Video content.","properties":{"internal":{"anyOf":[{"$ref":"#/$defs/JsonValue"},{"type":"null"}],"default":null},"type":{"const":"video","default":"video","title":"Type","type":"string"},"video":{"title":"Video","type":"string"},"format":{"enum":["mp4","mpeg","mov"],"title":"Format","type":"string"}},"required":["video","format"],"title":"ContentVideo","type":"object"},"DecisionEvent":{"description":"Event that indicates a something non-deterministic has happened.","properties":{"type":{"const":"decision_event","default":"decision_event","title":"Type","type":"string"},"id":{"title":"Id","type":"string"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"},"timestamp":{"format":"date-time","title":"Timestamp","type":"string"},"content":{"title":"Content"}},"required":["content"],"title":"DecisionEvent","type":"object"},"DocumentCitation":{"description":"A citation that refers to a page range in a document.","properties":{"cited_text":{"anyOf":[{"type":"string"},{"additionalItems":false,"items":[{"type":"integer"},{"type":"integer"}],"maxItems":2,"minItems":2,"type":"array"},{"type":"null"}],"default":null,"title":"Cited Text"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Title"},"internal":{"anyOf":[{"additionalProperties":{"$ref":"#/$defs/JsonValue"},"type":"object"},{"type":"null"}],"default":null,"title":"Internal"},"type":{"const":"document","default":"document","title":"Type","type":"string"},"range":{"anyOf":[{"$ref":"#/$defs/DocumentRange"},{"type":"null"}],"default":null}},"title":"DocumentCitation","type":"object"},"DocumentRange":{"description":"A range specifying a section of a document.","properties":{"type":{"enum":["block","page","char"],"title":"Type","type":"string"},"start_index":{"title":"Start Index","type":"integer"},"end_index":{"title":"End Index","type":"integer"}},"required":["type","start_index","end_index"],"title":"DocumentRange","type":"object"},"InfoEvent":{"description":"Event that adds information to the transcript.","properties":{"type":{"const":"info_event","default":"info_event","title":"Type","type":"string"},"id":{"title":"Id","type":"string"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"},"timestamp":{"format":"date-time","title":"Timestamp","type":"string"},"info":{"anyOf":[{"type":"string"},{"additionalProperties":true,"type":"object"}],"title":"Info"}},"required":["info"],"title":"InfoEvent","type":"object"},"JSONPatchEdit":{"description":"Edit that applies a JSON patch to the transcript.","properties":{"operation":{"const":"json_patch","default":"json_patch","title":"Operation","type":"string"},"patch":{"items":{"$ref":"#/$defs/JsonChange"},"title":"Patch","type":"array"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Name"}},"required":["patch"],"title":"JSONPatchEdit","type":"object"},"JSONSchema":{"description":"JSON Schema for type.","properties":{"type":{"anyOf":[{"enum":["string","integer","number","boolean","array","object","null"],"type":"string"},{"type":"null"}],"default":null,"title":"Type"},"format":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Format"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Description"},"default":{"default":null,"title":"Default"},"enum":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"default":null,"title":"Enum"},"items":{"anyOf":[{"$ref":"#/$defs/JSONSchema"},{"type":"null"}],"default":null},"properties":{"anyOf":[{"additionalProperties":{"$ref":"#/$defs/JSONSchema"},"type":"object"},{"type":"null"}],"default":null,"title":"Properties"},"additionalProperties":{"anyOf":[{"$ref":"#/$defs/JSONSchema"},{"type":"boolean"},{"type":"null"}],"default":null,"title":"Additionalproperties"},"anyOf":{"anyOf":[{"items":{"$ref":"#/$defs/JSONSchema"},"type":"array"},{"type":"null"}],"default":null,"title":"Anyof"},"required":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"default":null,"title":"Required"}},"title":"JSONSchema","type":"object"},"JsonChange":{"description":"Describes a change to data using JSON Patch format.","properties":{"op":{"enum":["remove","add","replace","move","test","copy"],"title":"Op","type":"string"},"path":{"title":"Path","type":"string"},"from":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"From"},"value":{"$ref":"#/$defs/JsonValue","default":null},"replaced":{"$ref":"#/$defs/JsonValue","default":null}},"required":["op","path"],"title":"JsonChange","type":"object"},"JsonValue":{},"JudgeOutput":{"description":"Output from the alignment judge evaluation.","properties":{"response":{"title":"Response","type":"string"},"summary":{"title":"Summary","type":"string"},"justification":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Justification"},"scores":{"additionalProperties":{"type":"integer"},"title":"Scores","type":"object"},"score_descriptions":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"default":null,"title":"Score Descriptions"}},"required":["response","summary","scores"],"title":"JudgeOutput","type":"object"},"Reset":{"description":"Edit that resets the transcript to the initial state.","properties":{"operation":{"const":"reset","default":"reset","title":"Operation","type":"string"},"new_messages":{"items":{"discriminator":{"mapping":{"assistant":"#/$defs/ChatMessageAssistant","system":"#/$defs/ChatMessageSystem","tool":"#/$defs/ChatMessageTool","user":"#/$defs/ChatMessageUser"},"propertyName":"role"},"oneOf":[{"$ref":"#/$defs/ChatMessageSystem"},{"$ref":"#/$defs/ChatMessageUser"},{"$ref":"#/$defs/ChatMessageAssistant"},{"$ref":"#/$defs/ChatMessageTool"}]},"title":"New Messages","type":"array"}},"title":"Reset","type":"object"},"Rollback":{"description":"Edit that rolls back the transcript count messages.","properties":{"operation":{"const":"rollback","default":"rollback","title":"Operation","type":"string"},"count":{"minimum":0,"title":"Count","type":"integer"},"to_id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"To Id"}},"required":["count"],"title":"Rollback","type":"object"},"ToolCall":{"properties":{"id":{"title":"Id","type":"string"},"function":{"title":"Function","type":"string"},"arguments":{"additionalProperties":true,"title":"Arguments","type":"object"},"internal":{"anyOf":[{"$ref":"#/$defs/JsonValue"},{"type":"null"}],"default":null},"parse_error":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Parse Error"},"view":{"anyOf":[{"$ref":"#/$defs/ToolCallContent"},{"type":"null"}],"default":null},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Type"}},"required":["id","function","arguments"],"title":"ToolCall","type":"object"},"ToolCallContent":{"description":"Content to include in tool call view.","properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Title"},"format":{"enum":["text","markdown"],"title":"Format","type":"string"},"content":{"title":"Content","type":"string"}},"required":["format","content"],"title":"ToolCallContent","type":"object"},"ToolCallError":{"properties":{"type":{"enum":["parsing","timeout","unicode_decode","permission","file_not_found","is_a_directory","limit","approval","unknown","output_limit"],"title":"Type","type":"string"},"message":{"title":"Message","type":"string"}},"required":["type","message"],"title":"ToolCallError","type":"object"},"ToolCreationEvent":{"description":"Event that creates a tool.","properties":{"type":{"const":"tool_creation_event","default":"tool_creation_event","title":"Type","type":"string"},"id":{"title":"Id","type":"string"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"},"timestamp":{"format":"date-time","title":"Timestamp","type":"string"},"model":{"title":"Model","type":"string"},"tool":{"$ref":"#/$defs/ToolDefinition"}},"required":["model","tool"],"title":"ToolCreationEvent","type":"object"},"ToolDefinition":{"description":"Serializable representation of a tool definition.\\n\\nThis holds only JSON-serializable fields. Convert to the library\'s\\nInspect `ToolDef` with `to_inspect_tooldef()` when calling model.generate.","properties":{"name":{"title":"Name","type":"string"},"description":{"title":"Description","type":"string"},"parameters":{"$ref":"#/$defs/ToolParams"},"parallel":{"default":true,"title":"Parallel","type":"boolean"},"options":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"default":null,"title":"Options"},"viewer":{"default":null,"title":"Viewer"},"model_input":{"default":null,"title":"Model Input"}},"required":["name","description","parameters"],"title":"ToolDefinition","type":"object"},"ToolParams":{"description":"Description of tool parameters object in JSON Schema format.","properties":{"type":{"const":"object","default":"object","title":"Type","type":"string"},"properties":{"type":"object","additionalProperties":{"$ref":"#/$defs/JSONSchema"},"title":"Properties"},"required":{"items":{"type":"string"},"title":"Required","type":"array"},"additionalProperties":{"default":false,"title":"Additionalproperties","type":"boolean"}},"title":"ToolParams","type":"object"},"TranscriptEvent":{"description":"Event that modifies a transcript.\\n\\nThe view field determines which transcript view(s) this event should be applied to.\\nCan be a single view name or a list of view names.","properties":{"type":{"const":"transcript_event","default":"transcript_event","title":"Type","type":"string"},"id":{"title":"Id","type":"string"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"},"timestamp":{"format":"date-time","title":"Timestamp","type":"string"},"view":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"View"},"edit":{"discriminator":{"mapping":{"add":"#/$defs/AddMessage","json_patch":"#/$defs/JSONPatchEdit","reset":"#/$defs/Reset","rollback":"#/$defs/Rollback"},"propertyName":"operation"},"oneOf":[{"$ref":"#/$defs/AddMessage"},{"$ref":"#/$defs/Rollback"},{"$ref":"#/$defs/Reset"},{"$ref":"#/$defs/JSONPatchEdit"}],"title":"Edit"}},"required":["view","edit"],"title":"TranscriptEvent","type":"object"},"TranscriptMetadata":{"properties":{"transcript_id":{"title":"Transcript Id","type":"string"},"auditor_model":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Auditor Model"},"target_model":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Target Model"},"created_at":{"format":"date-time","title":"Created At","type":"string"},"updated_at":{"format":"date-time","title":"Updated At","type":"string"},"version":{"const":"v3.0","default":"v3.0","title":"Version","type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Description"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"judge_output":{"anyOf":[{"$ref":"#/$defs/JudgeOutput"},{"type":"null"}],"default":null}},"required":["transcript_id","created_at","updated_at"],"title":"TranscriptMetadata","type":"object"},"UrlCitation":{"description":"A citation that refers to a URL.","properties":{"cited_text":{"anyOf":[{"type":"string"},{"additionalItems":false,"items":[{"type":"integer"},{"type":"integer"}],"maxItems":2,"minItems":2,"type":"array"},{"type":"null"}],"default":null,"title":"Cited Text"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Title"},"internal":{"anyOf":[{"additionalProperties":{"$ref":"#/$defs/JsonValue"},"type":"object"},{"type":"null"}],"default":null,"title":"Internal"},"type":{"const":"url","default":"url","title":"Type","type":"string"},"url":{"title":"Url","type":"string"}},"required":["url"],"title":"UrlCitation","type":"object"}}');
const properties = { "metadata": { "$ref": "#/$defs/TranscriptMetadata" }, "events": { "items": { "discriminator": { "mapping": { "decision_event": "#/$defs/DecisionEvent", "info_event": "#/$defs/InfoEvent", "tool_creation_event": "#/$defs/ToolCreationEvent", "transcript_event": "#/$defs/TranscriptEvent" }, "propertyName": "type" }, "oneOf": [{ "$ref": "#/$defs/TranscriptEvent" }, { "$ref": "#/$defs/ToolCreationEvent" }, { "$ref": "#/$defs/InfoEvent" }, { "$ref": "#/$defs/DecisionEvent" }] }, "title": "Events", "type": "array" }, "messages": { "items": {}, "title": "Messages", "type": "array" }, "target_messages": { "items": {}, "title": "Target Messages", "type": "array" } };
const required = ["metadata"];
const title = "Transcript";
const type = "object";
const transcriptSchema = {
$defs,
properties,
required,
title,
type
};
const ajv = new Ajv({
allErrors: true,
// Collect all errors, not just the first one
verbose: true,
// Include schema and data information in errors
strict: false
// Allow unknown keywords (for Pydantic-generated schemas)
});
addFormats(ajv);
ajv.addFormat("date-time", {
type: "string",
validate: function(dateTimeString) {
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/.test(dateTimeString)) {
return !isNaN(Date.parse(dateTimeString));
}
if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$/.test(dateTimeString)) {
const normalized = dateTimeString.replace(" ", "T");
return !isNaN(Date.parse(normalized));
}
return false;
}
});
const validateTranscript = ajv.compile(transcriptSchema);
function jsonPointerToPath(pointer) {
if (!pointer || pointer === "/") return "root";
const parts = pointer.split("/").filter(Boolean).map(decodeURIComponent);
let path2 = "";
for (const part of parts) {
const isIndex = /^\d+$/.test(part);
if (path2 === "") {
path2 = isIndex ? `[${part}]` : part;
} else if (isIndex) {
path2 += `[${part}]`;
} else {
path2 += `.${part}`;
}
}
return path2 || "root";
}
function validateTranscriptData(data) {
const valid = validateTranscript(data);
if (valid) {
return {
isValid: true,
valid: true,
errors: [],
transcript: data,
data
};
}
const errors = (validateTranscript.errors || []).map((error) => {
let message = error.message || "Unknown validation error";
if (error.keyword === "required" && error.params?.missingProperty) {
message = `missing required property "${error.params.missingProperty}"`;
} else if (error.keyword === "const" && error.params?.allowedValue !== void 0) {
message = `must be equal to constant (${JSON.stringify(error.params.allowedValue)})`;
}
return {
message,
path: jsonPointerToPath(error.instancePath || error.schemaPath || ""),
value: error.data,
schema: error.schema,
keyword: error.keyword,
params: error.params,
schemaPath: error.schemaPath
};
});
const partialData = extractTranscriptInfo(data);
return {
isValid: false,
valid: false,
errors,
partialData,
data
};
}
function formatValidationErrors(errors) {
if (errors.length === 0) return "No validation errors";
const errorMessages = errors.map((error, index) => {
const path2 = error.path || "root";
let valueSnippet = "";
try {
const json = JSON.stringify(error.value);
if (json && json.length <= 80) valueSnippet = ` (value: ${json})`;
} catch {
}
return `${index + 1}. ${path2}: ${error.message}${valueSnippet}`;
});
return `Schema validation failed (${errors.length} error${errors.length === 1 ? "" : "s"}):
${errorMessages.join("\n")}`;
}
function validateAndParseTranscript(content, filename) {
try {
const data = JSON.parse(content);
const result = validateTranscriptData(data);
if (!result.valid) {
console.error(
`Transcript validation failed${filename ? ` for ${filename}` : ""}:
` + formatValidationErrors(result.errors)
);
}
return result;
} catch (parseError) {
return {
isValid: false,
valid: false,
errors: [{
message: `JSON parsing failed: ${parseError instanceof Error ? parseError.message : "Unknown parse error"}`,
path: "root",
value: content,
schema: null
}],
data: null
};
}
}
function extractTranscriptInfo(data) {
return {
hasMetadata: !!(data && data.metadata),
hasEvents: !!(data && Array.isArray(data.events)),
eventCount: data && Array.isArray(data.events) ? data.events.length : 0,
version: data?.metadata?.version || null,
sessionId: data?.metadata?.session_id || null
};
}
if (typeof window !== "undefined") {
throw new Error("transcript-loader can only be used on the server side");
}
async function loadTranscriptFromFile(filePath) {
try {
const content = await promises.readFile(filePath, "utf-8");
const validationResult = validateAndParseTranscript(content, path.basename(filePath));
if (!validationResult.valid) {
const details = formatValidationErrors(validationResult.errors);
const err = new Error(`Invalid transcript file: ${filePath}
${details}`);
err.code = "TRANSCRIPT_VALIDATION_FAILED";
err.validation = validationResult;
throw err;
}
const transcript = validationResult.data;
if (!transcript.metadata?.transcript_id) {
throw new Error(`Missing transcript_id in metadata for file: ${filePath}`);
}
return createTranscriptDisplay(transcript, filePath);
} catch (error) {
if (error.code === "ENOENT") {
return null;
}
throw new Error(`Failed to load transcript from ${filePath}: ${error.message}`);
}
}
async function loadMetadataFromFile(filePath) {
try {
const content = await promises.readFile(filePath, "utf-8");
const data = JSON.parse(content);
if (!data?.metadata) {
throw new Error(`Invalid transcript structure in ${filePath}: missing metadata`);
}
return data.metadata;
} catch (error) {
if (error.code === "ENOENT") {
return null;
}
if (error instanceof SyntaxError) {
throw new Error(`Invalid JSON in ${filePath}: ${error.message}`);
}
throw new Error(`Failed to load metadata from ${filePath}: ${error.message}`);
}
}
const TRANSCRIPT_DIR = path.resolve(process.env.TRANSCRIPT_DIR || DEFAULT_TRANSCRIPT_DIR);
console.log(`[CONFIG] Transcript directory resolved to: ${TRANSCRIPT_DIR}`);
if (typeof window !== "undefined") {
throw new Error("transcript-cache can only be used on the server side");
}
class TranscriptCache {
metadataCache = /* @__PURE__ */ new Map();
fullTranscriptCache = /* @__PURE__ */ new Map();
maxFullTranscripts;
watcherInitialized = false;
watcher = null;
changeEmitter = new EventEmitter();
versionToken = `${Date.now()}`;
stats = {
metadataCount: 0,
fullTranscriptCount: 0,
cacheHits: 0,
cacheMisses: 0,
fileWatcherActive: false
};
constructor(maxFullTranscripts = 200) {
this.maxFullTranscripts = maxFullTranscripts;
console.log(`🗄️ [CACHE] TranscriptCache initialized with max ${maxFullTranscripts} full transcripts`);
}
/**
* Initialize file system watcher for the transcript directory
*/
async initializeWatcher(transcriptDir = DEFAULT_TRANSCRIPT_DIR) {
if (this.watcherInitialized) {
console.log("🔍 [CACHE] File watcher already initialized");
return;
}
try {
await promises.access(transcriptDir);
console.log(`🔍 [CACHE] Initializing file watcher for: ${transcriptDir}`);
this.watcher = watch(path.join(transcriptDir, "**/*.json"), {
ignored: /(^|[\/\\])\../,
// ignore dotfiles
persistent: true,
ignoreInitial: true
// Don't trigger for existing files
});
this.watcher.on("add", (filePath) => {
console.log(`📄 [CACHE] File added: ${filePath}`);
this.bumpVersionAndEmit("add", filePath);
this.invalidateFile(filePath);
}).on("change", (filePath) => {
console.log(`📝 [CACHE] File changed: ${filePath}`);
this.bumpVersionAndEmit("change", filePath);
this.invalidateFile(filePath);
}).on("unlink", (filePath) => {
console.log(`🗑️ [CACHE] File deleted: ${filePath}`);
this.bumpVersionAndEmit("unlink", filePath);
this.invalidateFile(filePath);
}).on("error", (error) => {
console.error("🚨 [CACHE] File watcher error:", error);
}).on("ready", () => {
console.log("✅ [CACHE] File watcher is ready");
this.stats.fileWatcherActive = true;
});
this.watcherInitialized = true;
} catch (error) {
console.error(`🚨 [CACHE] Failed to initialize file watcher for ${transcriptDir}:`, error);
}
}
/**
* Increment version token and emit a structured change event
*/
bumpVersionAndEmit(eventType, absoluteFilePath) {
this.versionToken = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
let relativePath;
try {
const baseDir = TRANSCRIPT_DIR;
relativePath = path.relative(baseDir, absoluteFilePath);
} catch {
relativePath = absoluteFilePath;
}
this.changeEmitter.emit("change", {
type: eventType,
absolutePath: absoluteFilePath,
relativePath,
version: this.versionToken,
updatedAt: Date.now()
});
}
/**
* Get metadata from cache or load from file
*/
async getMetadata(filePath) {
const normalizedPath = path.normalize(filePath);
const cached = this.metadataCache.get(normalizedPath);
if (cached && await this.isCacheValid(cached)) {
cached.accessTime = Date.now();
this.stats.cacheHits++;
return cached.data;
}
this.stats.cacheMisses++;
const metadata = await loadMetadataFromFile(normalizedPath);
if (metadata === null) {
return null;
}
const stat = await promises.stat(normalizedPath);
this.metadataCache.set(normalizedPath, {
data: metadata,
filePath: normalizedPath,
lastModified: stat.mtime.getTime(),
accessTime: Date.now()
});
this.stats.metadataCount = this.metadataCache.size;
return metadata;
}
/**
* Get full transcript from cache or load from file
*/
async getFullTranscript(filePath) {
const normalizedPath = path.normalize(filePath);
const cached = this.fullTranscriptCache.get(normalizedPath);
if (cached && await this.isCacheValid(cached)) {
cached.accessTime = Date.now();
this.stats.cacheHits++;
console.log(`💾 [CACHE] Full transcript cache hit: ${normalizedPath}`);
return cached.data;
}
console.log(`📁 [CACHE] Loading full transcript from file: ${normalizedPath}`);
this.stats.cacheMisses++;
const transcript = await loadTranscriptFromFile(normalizedPath);
if (transcript === null) {
return null;
}
const absoluteBaseDir = TRANSCRIPT_DIR;
const relativePath = path.relative(absoluteBaseDir, normalizedPath).replace(/\\/g, "/");
const updatedTranscript = {
...transcript,
_filePath: relativePath
};
const stat = await promises.stat(normalizedPath);
const entry = {
data: updatedTranscript,
filePath: normalizedPath,
lastModified: stat.mtime.getTime(),
accessTime: Date.now()
};
this.fullTranscriptCache.set(normalizedPath, entry);
await this.enforceFullTranscriptLimit();
this.stats.fullTranscriptCount = this.fullTranscriptCache.size;
return updatedTranscript;
}
/**
* Check if a cached entry is still valid by comparing modification times
*/
async isCacheValid(cached) {
try {
const stat = await promises.stat(cached.filePath);
return cached.lastModified >= stat.mtime.getTime();
} catch (error) {
return false;
}
}
/**
* Enforce LRU eviction for full transcript cache
*/
async enforceFullTranscriptLimit() {
if (this.fullTranscriptCache.size <= this.maxFullTranscripts) {
return;
}
const entries = Array.from(this.fullTranscriptCache.entries());
entries.sort(([, a], [, b]) => a.accessTime - b.accessTime);
const toRemove = entries.slice(0, entries.length - this.maxFullTranscripts);
for (const [key] of toRemove) {
this.fullTranscriptCache.delete(key);
console.log(`🗑️ [CACHE] Evicted full transcript from cache: ${key}`);
}
console.log(`📊 [CACHE] LRU eviction completed. Full transcript cache size: ${this.fullTranscriptCache.size}`);
}
/**
* Invalidate cache entries for a specific file
*/
invalidateFile(filePath) {
const normalizedPath = path.normalize(filePath);
let invalidated = false;
if (this.metadataCache.has(normalizedPath)) {
this.metadataCache.delete(normalizedPath);
invalidated = true;
console.log(`🗑️ [CACHE] Invalidated metadata cache for: ${normalizedPath}`);
}
if (this.fullTranscriptCache.has(normalizedPath)) {
this.fullTranscriptCache.delete(normalizedPath);
invalidated = true;
console.log(`🗑️ [CACHE] Invalidated full transcript cache for: ${normalizedPath}`);
}
if (invalidated) {
this.updateStats();
}
}
/**
* Subscribe to change events (file add/change/delete)
*/
onChange(listener) {
this.changeEmitter.on("change", listener);
return () => this.changeEmitter.off("change", listener);
}
/**
* Get current version token that changes whenever any transcript file changes
*/
getVersion() {
return this.versionToken;
}
/**
* Clear all cache entries
*/
clearCache() {
const metadataCount = this.metadataCache.size;
const fullTranscriptCount = this.fullTranscriptCache.size;
this.metadataCache.clear();
this.fullTranscriptCache.clear();
this.updateStats();
console.log(`🗑️ [CACHE] Cleared all cache entries (${metadataCount} metadata, ${fullTranscriptCount} full transcripts)`);
}
/**
* Get cache statistics
*/
getStats() {
return { ...this.stats };
}
/**
* Update internal statistics
*/
updateStats() {
this.stats.metadataCount = this.metadataCache.size;
this.stats.fullTranscriptCount = this.fullTranscriptCache.size;
}
/**
* Shutdown the cache and cleanup resources
*/
async shutdown() {
if (this.watcher) {
console.log("🔍 [CACHE] Shutting down file watcher");
await this.watcher.close();
this.watcher = null;
this.stats.fileWatcherActive = false;
}
this.clearCache();
this.watcherInitialized = false;
console.log("🗄️ [CACHE] TranscriptCache shutdown completed");
}
/**
* Preload metadata for all files in a directory (for bulk operations)
*/
async preloadMetadata(transcriptDir = DEFAULT_TRANSCRIPT_DIR) {
console.log(`🔄 [CACHE] Preloading metadata from: ${transcriptDir}`);
try {
const files = await this.findAllTranscriptFiles(transcriptDir);
console.log(`📁 [CACHE] Found ${files.length} transcript files to preload`);
const concurrency = 10;
const chunks = this.chunkArray(files, concurrency);
for (const chunk of chunks) {
await Promise.all(
chunk.map(async (filePath) => {
try {
await this.getMetadata(filePath);
} catch (error) {
console.warn(`⚠️ [CACHE] Failed to preload metadata for ${filePath}:`, error);
}
})
);
}
console.log(`✅ [CACHE] Preloaded metadata for ${this.metadataCache.size} transcripts`);
} catch (error) {
console.error("🚨 [CACHE] Failed to preload metadata:", error);
}
}
/**
* Find all transcript files recursively
*/
async findAllTranscriptFiles(dir) {
const files = [];
async function scanDir(currentDir) {
const entries = await promises.readdir(currentDir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(currentDir, entry.name);
if (entry.isDirectory()) {
await scanDir(fullPath);
} else if (entry.isFile() && entry.name.endsWith(".json")) {
files.push(fullPath);
}
}
}
await scanDir(dir);
return files;
}
/**
* Utility function to chunk an array
*/
chunkArray(array, chunkSize) {
const chunks = [];
for (let i = 0; i < array.length; i += chunkSize) {
chunks.push(array.slice(i, i + chunkSize));
}
return chunks;
}
}
let globalCache = null;
function getTranscriptCache() {
if (!globalCache) {
globalCache = new TranscriptCache();
}
return globalCache;
}
async function initializeGlobalCache(transcriptDir) {
const cache = getTranscriptCache();
await cache.initializeWatcher(transcriptDir);
}
async function shutdownGlobalCache() {
if (globalCache) {
await globalCache.shutdown();
globalCache = null;
}
}
const transcriptCache = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
TranscriptCache,
getTranscriptCache,
initializeGlobalCache,
shutdownGlobalCache
}, Symbol.toStringTag, { value: "Module" }));
export { TRANSCRIPT_DIR as T, getTranscriptCache as g, initializeGlobalCache as i, shutdownGlobalCache as s, transcriptCache as t };
//# sourceMappingURL=transcript-cache-CC28Y9w0.js.map