Source code for axio.context
"""ContextStore: protocol for conversation history storage."""
from __future__ import annotations
import copy
import logging
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Self
from uuid import uuid4
from .blocks import TextBlock
from .messages import Message
logger = logging.getLogger(__name__)
[docs]
@dataclass(frozen=True, slots=True)
class SessionInfo:
session_id: str
message_count: int
preview: str
created_at: str
input_tokens: int = 0
output_tokens: int = 0
[docs]
class ContextStore(ABC):
@property
def session_id(self) -> str:
"""Lazy-init UUID hex; works without calling super().__init__()."""
if "_session_id" not in self.__dict__:
self.__dict__["_session_id"] = uuid4().hex
return str(self.__dict__["_session_id"])
[docs]
@abstractmethod
async def append(self, message: Message) -> None: ...
[docs]
@abstractmethod
async def get_history(self) -> list[Message]: ...
[docs]
async def clear(self) -> None:
raise NotImplementedError(f"{type(self).__name__} does not support clear()")
[docs]
async def fork(self) -> ContextStore:
"""Returns a MemoryContextStore deep copy by default."""
messages = copy.deepcopy(await self.get_history())
in_tok, out_tok = await self.get_context_tokens()
store = MemoryContextStore(messages)
await store.set_context_tokens(in_tok, out_tok)
return store
[docs]
async def set_context_tokens(self, input_tokens: int, output_tokens: int) -> None:
"""No-op by default; tokens are silently dropped."""
[docs]
async def get_context_tokens(self) -> tuple[int, int]:
"""Returns (0, 0) by default."""
return 0, 0
[docs]
async def close(self) -> None:
"""No-op by default."""
[docs]
async def list_sessions(self) -> list[SessionInfo]:
"""List available sessions. Default: returns a single entry for the current session."""
history = await self.get_history()
in_tok, out_tok = await self.get_context_tokens()
preview = "(empty)"
for msg in history:
if msg.role == "user":
for block in msg.content:
if isinstance(block, TextBlock):
text = block.text
preview = text[:80] + ("..." if len(text) > 80 else "")
break
break
return [
SessionInfo(
session_id=self.session_id,
message_count=len(history),
preview=preview,
created_at="",
input_tokens=in_tok,
output_tokens=out_tok,
),
]
[docs]
async def add_context_tokens(self, input_tokens: int, output_tokens: int) -> None:
cur_in, cur_out = await self.get_context_tokens()
await self.set_context_tokens(cur_in + input_tokens, cur_out + output_tokens)
[docs]
@classmethod
async def from_history(cls, history: list[Message]) -> Self:
"""Create a new ContextStore pre-populated with *history*."""
store = cls()
for message in history:
await store.append(message)
return store
[docs]
@classmethod
async def from_context(cls, context: ContextStore) -> Self:
return await cls.from_history(await context.get_history())
[docs]
class MemoryContextStore(ContextStore):
"""Simple in-memory context store. fork() returns a deep copy."""
def __init__(self, history: list[Message] | None = None) -> None:
self._session_id = uuid4().hex
self._history: list[Message] = list(history or [])
self._input_tokens: int = 0
self._output_tokens: int = 0
@property
def session_id(self) -> str:
return self._session_id
[docs]
async def append(self, message: Message) -> None:
self._history.append(message)
[docs]
async def get_history(self) -> list[Message]:
return list(self._history)
[docs]
async def clear(self) -> None:
self._history.clear()
self._input_tokens = 0
self._output_tokens = 0
[docs]
async def fork(self) -> MemoryContextStore:
store = MemoryContextStore(copy.deepcopy(self._history))
store._input_tokens = self._input_tokens
store._output_tokens = self._output_tokens
return store
[docs]
async def set_context_tokens(self, input_tokens: int, output_tokens: int) -> None:
self._input_tokens = input_tokens
self._output_tokens = output_tokens
[docs]
async def get_context_tokens(self) -> tuple[int, int]:
return self._input_tokens, self._output_tokens
[docs]
async def close(self) -> None:
pass