Prompt History und Verbrauchsauswertung (#660), englische Hauptsprache (#661)

Prompt history: every run records its transcript (task, each model step with
tokens and cost, tool calls and results, engine follow-ups) - bounded in size,
scrubbed of recognisable secrets, system prompts stored once per text.
Retention in days per instance (default 14) and per agent (0 = never, -1 =
forever); an hourly janitor deletes what is past it, "never" also purges.

Usage analysis over run receipts: filter by period, agent, model, event, status,
cost and free text; sort by every column; totals over the whole filter; hourly
or daily series grouped by agent, model or event; markers for budget denials and
model changes; run detail with receipt, transcript, tool calls and approvals.

WebUI: Analysis > Token usage > Runs / Timeline, run detail, Prompt History with
retention settings. New code, comments and texts in English; texts come from
language packs (i18n/en.json, de.json) via modules/i18n.js. CLAUDE.md records
English as the main language.

Version 0.1.12.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-19 15:33:09 +02:00
co-authored by Claude Opus 5
parent 2f0361278b
commit 5adf4c1ca6
40 changed files with 3521 additions and 10 deletions
@@ -0,0 +1,103 @@
namespace ClawdDotNet.Core.Transcripts;
/// <summary>What a transcript entry is.</summary>
public static class TranscriptEntryKind
{
/// <summary>The task or message the run started with.</summary>
public const string User = "user";
/// <summary>A follow-up the engine itself sent (empty answer, missing result line).</summary>
public const string EngineNote = "engine";
/// <summary>One model response: text and/or tool calls, with the tokens of this step.</summary>
public const string Assistant = "assistant";
/// <summary>The result of one tool call.</summary>
public const string ToolResult = "tool_result";
/// <summary>Something that happened to the context, e.g. compaction.</summary>
public const string Event = "event";
}
/// <summary>One tool call as the model issued it.</summary>
public sealed record TranscriptToolCall(string Id, string Name, string Arguments);
/// <summary>
/// One entry of a run transcript. Texts are stored as the model saw or wrote them, after
/// secret scrubbing and clipping (<see cref="Clipped"/>).
/// </summary>
public sealed record TranscriptEntry
{
public string Kind { get; init; } = "";
public DateTime At { get; init; }
public string? Text { get; init; }
public IReadOnlyList<TranscriptToolCall>? ToolCalls { get; init; }
/// <summary>For <see cref="TranscriptEntryKind.ToolResult"/>: which call and tool.</summary>
public string? ToolCallId { get; init; }
public string? ToolName { get; init; }
/// <summary>For <see cref="TranscriptEntryKind.Assistant"/>: usage of this single step.</summary>
public int? PromptTokens { get; init; }
public int? CompletionTokens { get; init; }
public int? CachedTokens { get; init; }
public decimal? CostUsd { get; init; }
public string? FinishReason { get; init; }
/// <summary>The text was shortened to stay within the limits.</summary>
public bool Clipped { get; init; }
}
/// <summary>
/// Prompt and response of one run (#660), joined to receipts and audit via
/// <see cref="RunId"/>. The system prompt is stored once per distinct text and referenced
/// by <see cref="SystemPromptHash"/>.
/// </summary>
public sealed record RunTranscript
{
public string RunId { get; init; } = "";
public string AgentId { get; init; } = "";
public string Model { get; init; } = "";
public string Source { get; init; } = "";
public string? TaskId { get; init; }
public DateTime StartedAt { get; init; }
public DateTime? CompletedAt { get; init; }
public string Status { get; init; } = "";
public string? SystemPromptHash { get; init; }
public IReadOnlyList<TranscriptEntry> Entries { get; init; } = [];
/// <summary>Characters stored, after clipping.</summary>
public int SizeChars { get; init; }
/// <summary>The run exceeded the size limit; later entries are shortened or omitted.</summary>
public bool Truncated { get; init; }
}
/// <summary>A transcript without its entries — for lists.</summary>
public sealed record RunTranscriptSummary(
string RunId,
string AgentId,
string Model,
string Source,
string? TaskId,
DateTime StartedAt,
DateTime? CompletedAt,
string Status,
string Prompt,
string Response,
int SizeChars,
bool Truncated);
/// <summary>Filter for <see cref="IRunTranscriptStore.ListAsync"/>.</summary>
public sealed record TranscriptQuery
{
public string? AgentId { get; init; }
public DateTime? From { get; init; }
public DateTime? To { get; init; }
/// <summary>Full text over the whole transcript.</summary>
public string? Search { get; init; }
public int Limit { get; init; } = 50;
public int Offset { get; init; }
}