Files
oh-my-pi/packages/coding-agent/src/session/session-entries.ts
T
can1357 d820cf75bb feat: implemented token tracking and metadata options for compaction summaries
- Added `CompactionSummaryMessageOptions` interface to support optional fields and metadata in compaction summary messages.
- Included compaction method labels and before-after amount badge rendering in compaction views.
- Updated session maintenance and compaction methods to track and compute post-compaction token counts.
- Updated calls across agent and coding-agent packages to use the new compaction options object.
2026-08-20 07:18:43 +02:00

313 lines
11 KiB
TypeScript

import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
import type { ImageContent, MessageAttribution, ServiceTierByFamily, TextContent } from "@oh-my-pi/pi-ai";
import type { StructuredSubagentSchemaMode } from "../task/types";
import type { CompactionMethod } from "./compaction-methods";
export const CURRENT_SESSION_VERSION = 3;
export const SESSION_TITLE_SLOT_BYTES = 256;
export const SESSION_TITLE_SLOT_ENTRY_TYPE = "title";
export const TITLE_CHANGE_ENTRY_TYPE = "title_change";
export type SessionTitleSource = "auto" | "user";
/** Fixed-width first-line slot carrying the mutable current session title. */
export interface SessionTitleSlotEntry {
type: typeof SESSION_TITLE_SLOT_ENTRY_TYPE;
v: 1;
title: string;
source?: SessionTitleSource;
updatedAt: string;
pad: string;
}
export const EPHEMERAL_MODEL_CHANGE_ROLE = "fallback";
export interface SessionHeader {
type: "session";
version?: number; // v1 sessions don't have this
id: string;
title?: string; // Auto-generated title from first message
titleSource?: SessionTitleSource;
timestamp: string;
cwd: string;
/**
* Additional workspace directories beyond `cwd` (multi-root workspace).
* Absolute, normalized, deduplicated. Absent on legacy single-cwd sessions.
* See {@link SessionWorkspace} in `./session-workspace`.
*/
additionalDirectories?: string[];
parentSession?: string;
/** Prior absolute JSONL locations recorded by successful session moves. */
previousSessionFiles?: string[];
/** Provider prompt-cache identity inherited by exact-route full forks. */
providerPromptCacheKey?: string;
}
export interface NewSessionOptions {
parentSession?: string;
/** Provider prompt-cache identity to seed on the new session header. */
providerPromptCacheKey?: string;
/** Skip flushing the current session and delete it instead of saving. */
drop?: boolean;
/** Additional workspace directories to seed on the new session. */
additionalDirectories?: string[];
}
export interface SessionEntryBase {
type: string;
id: string;
parentId: string | null;
timestamp: string;
}
export interface SessionMessageEntry extends SessionEntryBase {
type: "message";
message: AgentMessage;
}
export interface ThinkingLevelChangeEntry extends SessionEntryBase {
type: "thinking_level_change";
thinkingLevel?: string | null;
/**
* The user-configured selector at the time of this change: `"auto"` when auto
* mode was active, otherwise the concrete level. Absent on entries written
* before auto-mode persistence existed; readers fall back to `thinkingLevel`.
*/
configured?: string | null;
}
export interface ModelChangeEntry extends SessionEntryBase {
type: "model_change";
/** Model in "provider/modelId" format */
model: string;
/** Role: "default", "smol", "slow", etc. Undefined treated as "default" */
role?: string;
/** True when this transition selected a retry-fallback model rather than the configured model. */
resolvedModelIsFallback?: boolean;
}
export interface ServiceTierChangeEntry extends SessionEntryBase {
type: "service_tier_change";
serviceTier: ServiceTierByFamily | null;
}
export interface CompactionEntry<T = unknown> extends SessionEntryBase {
type: "compaction";
summary: string;
shortSummary?: string;
firstKeptEntryId: string;
tokensBefore: number;
/** Estimated context tokens after the rewrite (display metadata). */
tokensAfter?: number;
/** Method that produced this entry; absent on legacy sessions and extension-provided compactions. */
method?: CompactionMethod;
/** Extension-specific data (e.g., ArtifactIndex, version markers for structured compaction) */
details?: T;
/** Hook-provided data to persist across compaction */
preserveData?: Record<string, unknown>;
/** True if generated by an extension, undefined/false if pi-generated (backward compatible) */
fromExtension?: boolean;
/**
* Dead-end warning from the post-pass progress guard: the pass completed
* but freed too little for maintenance to continue. Rendered on the
* compaction divider.
*/
warning?: string;
}
export interface BranchSummaryEntry<T = unknown> extends SessionEntryBase {
type: "branch_summary";
fromId: string;
summary: string;
/** Extension-specific data (not sent to LLM) */
details?: T;
/** True if generated by an extension, false if pi-generated */
fromExtension?: boolean;
}
/**
* Pure marker entry recorded by `/clear` (resetSessionContext). It carries no
* payload — its presence on the branch is a durable boundary the collapsed
* live transcript and the model-context rebuild start emission after, so a
* rebuild (theme change, focus attach, /shake, resume) does not resurrect the
* pre-reset conversation. The on-disk record and the plain `transcript:true`
* export path keep the full pre-reset history.
*/
export interface ResetBoundaryEntry extends SessionEntryBase {
type: "reset_boundary";
}
/**
* Custom entry for extensions to store extension-specific data in the session.
* Use customType to identify your extension's entries.
*
* Purpose: Persist extension state across session reloads. On reload, extensions can
* scan entries for their customType and reconstruct internal state.
*
* Does NOT participate in LLM context (ignored by buildSessionContext).
* For injecting content into context, see CustomMessageEntry.
*/
export interface CustomEntry<T = unknown> extends SessionEntryBase {
type: "custom";
customType: string;
data?: T;
}
/** Label entry for user-defined bookmarks/markers on entries. */
export interface LabelEntry extends SessionEntryBase {
type: "label";
targetId: string;
label: string | undefined;
}
/** Append-only audit entry recording a session title change. */
export interface TitleChangeEntry extends SessionEntryBase {
type: typeof TITLE_CHANGE_ENTRY_TYPE;
title: string;
previousTitle?: string;
source: SessionTitleSource;
trigger?: string;
}
declare module "@oh-my-pi/pi-agent-core/compaction/entries" {
interface CustomCompactionSessionEntries {
titleChange: TitleChangeEntry;
credentialPin: CredentialPinEntry;
}
}
/** TTSR injection entry - tracks which time-traveling rules have been injected this session. */
export interface TtsrInjectionEntry extends SessionEntryBase {
type: "ttsr_injection";
/** Names of rules that were injected */
injectedRules: string[];
}
/**
* Records which OAuth account served this session's requests for a provider.
*
* Provider prompt caches (Anthropic in particular) are account-scoped, and the
* auth store's session-sticky routing is process-local under a remote auth
* broker, so resume must re-pin the same account to reuse the warm cache
* prefix. Stores a sha-256 of the account + billing-scope tuple instead of
* the raw email/uuid/org; note an unsalted digest of a guessable email is
* still linkable, so exported sessions are pseudonymous, not anonymous.
*/
export interface CredentialPinEntry extends SessionEntryBase {
type: "credential_pin";
/** Provider id the pin applies to (e.g. "anthropic"). */
provider: string;
/** `credentialPinHash()` of the serving account's identity + scope tuple. */
hash: string;
}
/** Session init entry - captures initial context for subagent sessions (debugging/replay). */
export interface SessionInitEntry extends SessionEntryBase {
type: "session_init";
/** Full system prompt sent to the model */
systemPrompt: string;
/** Initial task/user message */
task: string;
/** Tools available to the agent */
tools: string[];
/** Agent definition name (for example `scout` or `reviewer`). */
agent?: string;
/** Semantic model role declared by the agent, retained even after concrete model resolution. */
modelRole?: string;
/** Initially resolved provider/model selector for historical display. */
resolvedModel?: string;
/** Whether the agent definition is read-only, allowing an exact zero-LoC attribution. */
readOnly?: boolean;
/** Output schema if structured output was requested. */
outputSchema?: unknown;
/** Enforcement policy recorded with the output schema for faithful revival. */
outputSchemaMode?: StructuredSubagentSchemaMode;
/** Whether revival must retain only the explicitly persisted tool names. */
restrictToolNames?: boolean;
/** Spawn allowlist the subagent ran with ("" = none, "*" = any, else CSV); absent on pre-spawns files. */
spawns?: string;
/** The agent's `readSummarize` setting (`false` = read summarization disabled); absent uses the session default. */
readSummarize?: boolean;
/** Effective advisor for this subagent: `"on"` = advisor-role model, else an explicit model pattern; absent = unadvised. */
advisor?: string;
}
/** Mode change entry - tracks agent mode transitions (e.g. plan mode). */
export interface ModeChangeEntry extends SessionEntryBase {
type: "mode_change";
/** Current mode name, or "none" when exiting a mode */
mode: string;
/** Optional mode-specific data (e.g. plan file path) */
data?: Record<string, unknown>;
}
/**
* Custom message entry for extensions to inject messages into LLM context.
* Use customType to identify your extension's entries.
*
* Unlike CustomEntry, this DOES participate in LLM context.
* The content participates in LLM context through convertToLlm().
* Use details for extension-specific metadata (not sent to LLM).
*
* display controls TUI rendering:
* - false: hidden entirely
* - true: rendered with distinct styling (different from user messages)
*/
export interface CustomMessageEntry<T = unknown> extends SessionEntryBase {
type: "custom_message";
customType: string;
content: string | (TextContent | ImageContent)[];
details?: T;
display: boolean;
/** Who initiated this message for billing/attribution semantics. */
attribution?: MessageAttribution;
}
/** Session entry - has id/parentId for tree structure (returned by "read" methods in SessionManager) */
export type SessionEntry =
| SessionMessageEntry
| ThinkingLevelChangeEntry
| ModelChangeEntry
| ServiceTierChangeEntry
| CompactionEntry
| BranchSummaryEntry
| CustomEntry
| CustomMessageEntry
| LabelEntry
| TitleChangeEntry
| TtsrInjectionEntry
| SessionInitEntry
| ModeChangeEntry
| CredentialPinEntry
| ResetBoundaryEntry;
/** Raw logical file entry after loaders strip any fixed-width title slot. */
export type FileEntry = SessionHeader | SessionEntry;
/** Physical JSONL entry before slot-aware loaders fold the title slot. */
export type RawFileEntry = SessionTitleSlotEntry | FileEntry;
/** Tree node for getTree() - defensive copy of session structure */
export interface SessionTreeNode {
entry: SessionEntry;
children: SessionTreeNode[];
/** Resolved label for this entry, if any */
label?: string;
}
export interface UsageStatistics {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
totalTokens: number;
orchestrationInput: number;
orchestrationOutput: number;
orchestrationCacheRead: number;
premiumRequests: number;
cost: number;
}