reasonix 0.0.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { WriteStream } from 'node:fs';
2
+
1
3
  /**
2
4
  * Retry layer for DeepSeek API calls.
3
5
  *
@@ -436,6 +438,12 @@ interface LoopEvent {
436
438
  content: string;
437
439
  reasoningDelta?: string;
438
440
  toolName?: string;
441
+ /**
442
+ * Raw JSON-string arguments the model sent for a tool call (role === "tool").
443
+ * Populated so transcripts can persist *why* a tool was called, not just
444
+ * what it returned. Needed by `reasonix diff` to explain divergences.
445
+ */
446
+ toolArgs?: string;
439
447
  stats?: TurnStats;
440
448
  planState?: TypedPlanState;
441
449
  repair?: RepairReport;
@@ -561,6 +569,207 @@ declare function deleteSession(name: string): boolean;
561
569
  */
562
570
  declare function loadDotenv(path?: string): void;
563
571
 
572
+ /**
573
+ * Transcript format — the canonical "audit log" of a Reasonix session.
574
+ *
575
+ * Design split:
576
+ * - Session file (`~/.reasonix/sessions/<name>.jsonl`) stores only the
577
+ * `ChatMessage`s the model needs to resume. See session.ts.
578
+ * - Transcript file (this module) stores every LoopEvent with usage, cost,
579
+ * model, and prefix fingerprint attached where available — enough for
580
+ * replay and diff to reconstruct economics.
581
+ *
582
+ * The two are different contracts: sessions are the user's *memory*;
583
+ * transcripts are the *receipts*. Don't conflate them.
584
+ *
585
+ * Backward compatibility: all fields beyond {ts, turn, role, content} are
586
+ * optional on read. A v0.1 transcript (pre-usage) still parses and renders
587
+ * — it just shows cost/cache as n/a.
588
+ */
589
+
590
+ interface TranscriptRecord {
591
+ /** ISO-8601 timestamp at emit time. */
592
+ ts: string;
593
+ /** 1-based turn number within the session. */
594
+ turn: number;
595
+ /** LoopEvent role — "assistant_delta" | "assistant_final" | "tool" | "done" | ... */
596
+ role: string;
597
+ /** For assistant events, the final (or delta) text; for tool events, the tool result. */
598
+ content: string;
599
+ /** Tool name (role === "tool"). */
600
+ tool?: string;
601
+ /** JSON-string args the model sent for a tool call (role === "tool"). Persisted so diff can explain *why* two runs made different calls. */
602
+ args?: string;
603
+ /** DeepSeek token-usage snapshot (role === "assistant_final"). */
604
+ usage?: RawUsage;
605
+ /** USD cost of this turn (role === "assistant_final"). */
606
+ cost?: number;
607
+ /** Model id that produced this turn. */
608
+ model?: string;
609
+ /**
610
+ * The ImmutablePrefix fingerprint at this turn. Lets diff prove two runs
611
+ * share a prefix — i.e. any cache-hit delta is attributable to log
612
+ * stability, not to a different system prompt.
613
+ */
614
+ prefixHash?: string;
615
+ /** Optional error message (role === "error"). */
616
+ error?: string;
617
+ }
618
+ interface TranscriptMeta {
619
+ /**
620
+ * Optional metadata written as the first line of a transcript. Lets
621
+ * downstream tooling know what it's reading without guessing.
622
+ * Recognized by a special role "_meta".
623
+ */
624
+ version: 1;
625
+ source: string;
626
+ model?: string;
627
+ task?: string;
628
+ mode?: string;
629
+ repeat?: number;
630
+ startedAt: string;
631
+ }
632
+ interface ReadTranscriptResult {
633
+ meta: TranscriptMeta | null;
634
+ records: TranscriptRecord[];
635
+ }
636
+ /**
637
+ * Build a TranscriptRecord from a LoopEvent. Extra fields (model,
638
+ * prefixHash) that the LoopEvent doesn't carry are passed in separately
639
+ * because they're session-level, not event-level.
640
+ */
641
+ declare function recordFromLoopEvent(ev: LoopEvent, extra: {
642
+ model: string;
643
+ prefixHash: string;
644
+ }): TranscriptRecord;
645
+ /**
646
+ * Append a record to an open write stream. Caller owns the stream lifecycle.
647
+ */
648
+ declare function writeRecord(stream: WriteStream, record: TranscriptRecord): void;
649
+ /**
650
+ * Write a _meta line to an open write stream. Call exactly once, at the top.
651
+ */
652
+ declare function writeMeta(stream: WriteStream, meta: TranscriptMeta): void;
653
+ /**
654
+ * Convenience: open a stream, write meta, return stream.
655
+ */
656
+ declare function openTranscriptFile(path: string, meta: TranscriptMeta): WriteStream;
657
+ /**
658
+ * Parse a transcript file. Returns meta (if the first line is a _meta record)
659
+ * and the full record list.
660
+ *
661
+ * Robustness contract:
662
+ * - Empty lines are skipped.
663
+ * - Malformed JSON lines are skipped silently (do not crash on partial
664
+ * files — live chats may be mid-write).
665
+ * - Records missing optional fields still parse — they're just rendered
666
+ * with n/a where the optional value would go.
667
+ */
668
+ declare function readTranscript(path: string): ReadTranscriptResult;
669
+ declare function parseTranscript(raw: string): ReadTranscriptResult;
670
+
671
+ /**
672
+ * Replay — reconstruct session economics from a transcript file.
673
+ *
674
+ * Given a transcript written by App.tsx or the bench runner, rebuild a
675
+ * SessionSummary-compatible aggregate (turn count, total cost, cache-hit
676
+ * ratio, vs-Claude estimate) without replaying the LLM calls.
677
+ *
678
+ * The whole point is offline auditing: a reader should be able to reproduce
679
+ * the headline numbers from a transcript alone, without an API key.
680
+ */
681
+
682
+ interface ReplayStats extends SessionSummary {
683
+ /** Per-turn stats, in turn order. Only assistant_final records contribute. */
684
+ perTurn: TurnStats[];
685
+ /** Unique models that appeared in the transcript's assistant_final records. */
686
+ models: string[];
687
+ /** Unique prefix hashes that appeared. Length > 1 means the prefix churned (cache-hostile). */
688
+ prefixHashes: string[];
689
+ /** Count of user-role records (user turns issued). */
690
+ userTurns: number;
691
+ /** Count of tool-role records (tool calls executed). */
692
+ toolCalls: number;
693
+ }
694
+ /**
695
+ * Parse a transcript file and compute replay stats. Throws only on I/O
696
+ * errors; malformed lines inside the file are skipped silently.
697
+ */
698
+ declare function replayFromFile(path: string): {
699
+ parsed: ReadTranscriptResult;
700
+ stats: ReplayStats;
701
+ };
702
+ declare function computeReplayStats(records: TranscriptRecord[]): ReplayStats;
703
+
704
+ /**
705
+ * Diff — compare two transcripts and produce a summary + divergence report.
706
+ *
707
+ * Two transcripts are "comparable" when they stem from the same task (or
708
+ * the same user prompt). Alignment is by turn number: assistant_final #N
709
+ * in A pairs with assistant_final #N in B. If one side ran more turns, the
710
+ * extras are labeled "only in A" / "only in B".
711
+ *
712
+ * What we compute:
713
+ * - Aggregate deltas: turns, tool calls, cache hit, cost, token counts
714
+ * - First divergence: the lowest turn where A and B's tool calls or
715
+ * assistant text differ meaningfully
716
+ * - Prefix-stability story: how many unique prefix hashes each side used
717
+ *
718
+ * Non-goals (deliberately):
719
+ * - LLM-judge quality comparison
720
+ * - Per-token delta rendering — not useful at the fidelity we're at
721
+ * - Embedding similarity — Levenshtein ratio is cheap and good enough
722
+ */
723
+
724
+ interface DiffSide {
725
+ label: string;
726
+ meta: ReadTranscriptResult["meta"];
727
+ records: TranscriptRecord[];
728
+ stats: ReplayStats;
729
+ }
730
+ interface TurnPair {
731
+ turn: number;
732
+ aAssistant?: TranscriptRecord;
733
+ bAssistant?: TranscriptRecord;
734
+ aTools: TranscriptRecord[];
735
+ bTools: TranscriptRecord[];
736
+ /**
737
+ * Classification of the pair:
738
+ * "match" — both sides present, text & tool calls within threshold
739
+ * "diverge" — both sides present, but text or tool calls differ
740
+ * "only_in_a" — assistant_final in A but not B
741
+ * "only_in_b" — assistant_final in B but not A
742
+ */
743
+ kind: "match" | "diverge" | "only_in_a" | "only_in_b";
744
+ /** When kind === "diverge", a short one-liner pointing at what differs. */
745
+ divergenceNote?: string;
746
+ }
747
+ interface DiffReport {
748
+ a: DiffSide;
749
+ b: DiffSide;
750
+ pairs: TurnPair[];
751
+ firstDivergenceTurn: number | null;
752
+ }
753
+ declare function diffTranscripts(a: {
754
+ label: string;
755
+ parsed: ReadTranscriptResult;
756
+ }, b: {
757
+ label: string;
758
+ parsed: ReadTranscriptResult;
759
+ }): DiffReport;
760
+ /**
761
+ * Normalized Levenshtein similarity ratio in [0, 1]. 1 = identical.
762
+ * Early-exits for long strings (> 2000 chars) with a cheap token-overlap
763
+ * estimate to keep diff fast on chatty transcripts.
764
+ */
765
+ declare function similarity(a: string, b: string): number;
766
+ interface RenderOptions {
767
+ /** Monochrome output (for file redirection or piping). Defaults to true. */
768
+ monochrome?: boolean;
769
+ }
770
+ declare function renderSummaryTable(report: DiffReport, _opts?: RenderOptions): string;
771
+ declare function renderMarkdown(report: DiffReport): string;
772
+
564
773
  /**
565
774
  * User-level config storage for the Reasonix CLI.
566
775
  *
@@ -588,6 +797,6 @@ declare function redactKey(key: string): string;
588
797
 
589
798
  /** Reasonix — DeepSeek-native agent framework. Library entry point. */
590
799
 
591
- declare const VERSION = "0.0.1";
800
+ declare const VERSION = "0.2.0";
592
801
 
593
- export { AppendOnlyLog, type BranchOptions, type BranchProgress, type BranchResult, type BranchSample, type BranchSelector, type BranchSummary, CacheFirstLoop, type CacheFirstLoopOptions, type ChatMessage, type ChatResponse, DeepSeekClient, type DeepSeekClientOptions, type EventRole, type FlattenDecision, type HarvestOptions, ImmutablePrefix, type ImmutablePrefixOptions, type JSONSchema, type LoopEvent, type ReasonixConfig, type ReconfigurableOptions, type RepairReport, type RetryInfo, type RetryOptions, type Role, type ScavengeOptions, type ScavengeResult, type SessionInfo, SessionStats, type SessionSummary, StormBreaker, type StreamChunk, type ToolCall, ToolCallRepair, type ToolCallRepairOptions, type ToolDefinition, type ToolFunctionSpec, ToolRegistry, type ToolSpec, type TruncationRepairResult, type TurnStats, type TypedPlanState, Usage, VERSION, VolatileScratch, aggregateBranchUsage, analyzeSchema, appendSessionMessage, claudeEquivalentCost, costUsd, defaultConfigPath, defaultSelector, deleteSession, emptyPlanState, fetchWithRetry, flattenSchema, harvest, isPlanStateEmpty, isPlausibleKey, listSessions, loadApiKey, loadDotenv, loadSessionMessages, nestArguments, readConfig, redactKey, repairTruncatedJson, runBranches, sanitizeName as sanitizeSessionName, saveApiKey, scavengeToolCalls, sessionPath, sessionsDir, writeConfig };
802
+ export { AppendOnlyLog, type BranchOptions, type BranchProgress, type BranchResult, type BranchSample, type BranchSelector, type BranchSummary, CacheFirstLoop, type CacheFirstLoopOptions, type ChatMessage, type ChatResponse, DeepSeekClient, type DeepSeekClientOptions, type RenderOptions as DiffRenderOptions, type DiffReport, type DiffSide, type EventRole, type FlattenDecision, type HarvestOptions, ImmutablePrefix, type ImmutablePrefixOptions, type JSONSchema, type LoopEvent, type ReadTranscriptResult, type ReasonixConfig, type ReconfigurableOptions, type RepairReport, type ReplayStats, type RetryInfo, type RetryOptions, type Role, type ScavengeOptions, type ScavengeResult, type SessionInfo, SessionStats, type SessionSummary, StormBreaker, type StreamChunk, type ToolCall, ToolCallRepair, type ToolCallRepairOptions, type ToolDefinition, type ToolFunctionSpec, ToolRegistry, type ToolSpec, type TranscriptMeta, type TranscriptRecord, type TruncationRepairResult, type TurnPair, type TurnStats, type TypedPlanState, Usage, VERSION, VolatileScratch, aggregateBranchUsage, analyzeSchema, appendSessionMessage, claudeEquivalentCost, computeReplayStats, costUsd, defaultConfigPath, defaultSelector, deleteSession, diffTranscripts, emptyPlanState, fetchWithRetry, flattenSchema, harvest, isPlanStateEmpty, isPlausibleKey, listSessions, loadApiKey, loadDotenv, loadSessionMessages, nestArguments, openTranscriptFile, parseTranscript, readConfig, readTranscript, recordFromLoopEvent, redactKey, renderMarkdown as renderDiffMarkdown, renderSummaryTable as renderDiffSummary, repairTruncatedJson, replayFromFile, runBranches, sanitizeName as sanitizeSessionName, saveApiKey, scavengeToolCalls, sessionPath, sessionsDir, similarity, writeConfig, writeMeta, writeRecord };