@sema-agent/core
Version:
Stateless, task-oriented AI agent core
398 lines (397 loc) • 15.9 kB
JavaScript
import { asAgentMessage, createCompactionSummaryMessage, createCustomMessage, } from "../harness/messages.js";
import { SessionError, isSyntheticApiErrorMessage, isValidModelChange, normalizeAnnouncedListing, normalizeCompactionStateCarrier, normalizeGitAnnouncement, normalizeReminderMark, normalizeWorkspaceState } from "../harness/types.js";
import { normalizePromptEpoch } from "../../prompt-assembly/epoch.js";
import { budgetInvokedSkillsRetention, readElidedMessages, readRetainedInvokedSkills, readUnsummarizedMessages, renderInvokedSkillsRetention, } from "../compaction/utils.js";
const RETENTION_CLAMP_DEFAULT_CHARS_PER_TOKEN = 3;
const RETENTION_CLAMP_WINDOW_FRACTION = 0.5;
export function buildSessionContext(pathEntries, opts) {
let thinkingLevel = "off";
let model = null;
let compaction = null;
for (const entry of pathEntries) {
if (entry.type !== "compaction")
continue;
const carried = normalizeCompactionStateCarrier(entry.details);
if (carried.thinkingLevel !== undefined)
thinkingLevel = carried.thinkingLevel;
if (carried.model !== undefined)
model = carried.model;
}
for (const entry of pathEntries) {
if (entry.type === "thinking_level_change") {
thinkingLevel = entry.thinkingLevel;
}
else if (entry.type === "model_change") {
model = { provider: entry.provider, modelId: entry.modelId };
}
else if (entry.type === "message" && entry.message.role === "assistant") {
if (!isSyntheticApiErrorMessage(entry.message) &&
isValidModelChange({ provider: entry.message.provider, modelId: entry.message.model })) {
model = { provider: entry.message.provider, modelId: entry.message.model };
}
}
else if (entry.type === "compaction") {
compaction = entry;
}
}
const messages = [];
const isAssistantMessage = (m) => m.role === "assistant";
const stripAssistantUsage = (message) => {
if (!isAssistantMessage(message)) {
return message;
}
return {
...message,
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
};
};
const appendMessage = (entry, stripUsage = false) => {
if (entry.type === "message") {
messages.push(stripUsage ? stripAssistantUsage(entry.message) : entry.message);
}
else if (entry.type === "custom_message") {
messages.push(asAgentMessage(createCustomMessage(entry.customType, entry.content, entry.display, entry.details, entry.timestamp)));
}
};
if (compaction) {
let retainedSkills = readRetainedInvokedSkills(compaction.details);
if (opts?.windowTokens !== undefined && opts.windowTokens > 0 && retainedSkills.length > 0) {
const cpt = opts.charsPerToken ?? RETENTION_CLAMP_DEFAULT_CHARS_PER_TOKEN;
const budgetChars = Math.max(0, Math.floor(opts.windowTokens * cpt * RETENTION_CLAMP_WINDOW_FRACTION) - compaction.summary.length);
retainedSkills = budgetInvokedSkillsRetention(retainedSkills, budgetChars);
}
const retainedSkillsBlock = renderInvokedSkillsRetention(retainedSkills);
messages.push(asAgentMessage(createCompactionSummaryMessage(compaction.summary + retainedSkillsBlock, compaction.tokensBefore, compaction.timestamp, readElidedMessages(compaction.details), readUnsummarizedMessages(compaction.details))));
const compactionIdx = pathEntries.findIndex((e) => e.type === "compaction" && e.id === compaction.id);
let foundFirstKept = false;
for (let i = 0; i < compactionIdx; i++) {
const entry = pathEntries[i];
if (entry.id === compaction.firstKeptEntryId) {
foundFirstKept = true;
}
if (foundFirstKept) {
appendMessage(entry, true);
}
}
if (!foundFirstKept) {
throw new SessionError("invalid_session", `Compaction ${compaction.id} firstKeptEntryId (${compaction.firstKeptEntryId}) not found before the compaction entry — session may be corrupt or need migration`);
}
for (let i = compactionIdx + 1; i < pathEntries.length; i++) {
appendMessage(pathEntries[i]);
}
}
else {
for (const entry of pathEntries) {
appendMessage(entry);
}
}
return { messages, thinkingLevel, model };
}
export class StoredSession {
storage;
constructor(storage) {
this.storage = storage;
}
getMetadata() {
return this.storage.getMetadata();
}
getStorage() {
return this.storage;
}
getLeafId() {
return this.storage.getLeafId();
}
getEntry(id) {
return this.storage.getEntry(id);
}
getEntries() {
return this.storage.getEntries();
}
async getBranch(fromId) {
const leafId = fromId ?? (await this.storage.getLeafId());
return this.storage.getPathToRoot(leafId);
}
async buildContext(opts) {
return buildSessionContext(await this.getBranch(), opts);
}
getLabel(id) {
return this.storage.getLabel(id);
}
async getSessionName() {
const entries = await this.storage.findEntries("session_info");
return entries[entries.length - 1]?.name?.trim() || undefined;
}
async appendTypedEntry(entry) {
await this.storage.appendEntry(entry, { expectedLeafId: entry.parentId });
return entry.id;
}
async appendMessage(message) {
return this.appendTypedEntry({
type: "message",
id: await this.storage.createEntryId(),
parentId: await this.storage.getLeafId(),
timestamp: new Date().toISOString(),
message,
});
}
async appendThinkingLevelChange(thinkingLevel) {
return this.appendTypedEntry({
type: "thinking_level_change",
id: await this.storage.createEntryId(),
parentId: await this.storage.getLeafId(),
timestamp: new Date().toISOString(),
thinkingLevel,
});
}
async appendModelChange(provider, modelId) {
return this.appendTypedEntry({
type: "model_change",
id: await this.storage.createEntryId(),
parentId: await this.storage.getLeafId(),
timestamp: new Date().toISOString(),
provider,
modelId,
});
}
async appendPromptEpoch(descriptor) {
return this.appendTypedEntry({
type: "prompt_epoch",
id: await this.storage.createEntryId(),
parentId: await this.storage.getLeafId(),
timestamp: new Date().toISOString(),
...descriptor,
});
}
async appendAnnouncedListing(snapshot) {
const shaped = normalizeAnnouncedListing(snapshot);
if (!shaped) {
throw new SessionError("invalid_entry", "appendAnnouncedListing: snapshot is not a structurally valid announced-listing (string arrays, bounded)");
}
return this.appendTypedEntry({
type: "announced_listing",
id: await this.storage.createEntryId(),
parentId: await this.storage.getLeafId(),
timestamp: new Date().toISOString(),
...shaped,
});
}
async appendGitAnnouncement(state) {
const shaped = normalizeGitAnnouncement(state);
if (!shaped) {
throw new SessionError("invalid_entry", "appendGitAnnouncement: state is not a structurally valid git announcement (closed kind, sha256 hash, bounded entryId)");
}
return this.appendTypedEntry({
type: "git_announcement",
id: await this.storage.createEntryId(),
parentId: await this.storage.getLeafId(),
timestamp: new Date().toISOString(),
...shaped,
});
}
async getGitAnnouncement() {
const branch = await this.getBranch();
for (let i = branch.length - 1; i >= 0; i--) {
const entry = branch[i];
let carried;
if (entry.type === "git_announcement") {
carried = normalizeGitAnnouncement(entry);
}
else if (entry.type === "compaction") {
carried = normalizeGitAnnouncement(entry.details?.gitAnnouncement);
}
if (carried === undefined)
continue;
const announced = carried.pending !== true && carried.entryId !== undefined && gitFrameContextVisible(branch, carried.entryId);
return announced
? { kind: carried.kind, hash: carried.hash, status: "announced", entryId: carried.entryId }
: { kind: carried.kind, hash: carried.hash, status: "pending" };
}
return undefined;
}
async appendWorkspaceState(state) {
const shaped = normalizeWorkspaceState(state);
if (!shaped) {
throw new SessionError("invalid_entry", "appendWorkspaceState: state is not a structurally valid workspace-state snapshot (bounded absolute paths + a git sha)");
}
return this.appendTypedEntry({
type: "workspace_state",
id: await this.storage.createEntryId(),
parentId: await this.storage.getLeafId(),
timestamp: new Date().toISOString(),
...shaped,
});
}
async appendReminderMark(mark) {
const shaped = normalizeReminderMark({ mark });
if (shaped === undefined) {
throw new SessionError("invalid_entry", "appendReminderMark: mark is not a bounded url-safe token");
}
return this.appendTypedEntry({
type: "reminder_mark",
id: await this.storage.createEntryId(),
parentId: await this.storage.getLeafId(),
timestamp: new Date().toISOString(),
mark: shaped,
});
}
async getReminderMark() {
const branch = await this.getBranch();
for (let i = branch.length - 1; i >= 0; i--) {
const entry = branch[i];
if (entry.type === "reminder_mark") {
const shaped = normalizeReminderMark(entry);
if (shaped !== undefined)
return shaped;
}
}
return undefined;
}
async getWorkspaceState() {
const branch = await this.getBranch();
for (let i = branch.length - 1; i >= 0; i--) {
const entry = branch[i];
if (entry.type === "workspace_state") {
const shaped = normalizeWorkspaceState(entry);
if (shaped)
return shaped;
}
}
return undefined;
}
async getAnnouncedListing() {
const branch = await this.getBranch();
for (let i = branch.length - 1; i >= 0; i--) {
const entry = branch[i];
if (entry.type === "announced_listing") {
const shaped = normalizeAnnouncedListing(entry);
if (shaped)
return shaped;
}
else if (entry.type === "compaction") {
const restated = normalizeAnnouncedListing(entry.details?.announcedListings);
if (restated)
return restated;
}
}
return undefined;
}
async getPromptEpoch() {
const branch = await this.getBranch();
for (let i = branch.length - 1; i >= 0; i--) {
const entry = branch[i];
if (entry.type === "compaction") {
const restated = normalizePromptEpoch(entry.details?.promptEpoch);
if (restated)
return restated;
}
else if (entry.type === "prompt_epoch") {
const shaped = normalizePromptEpoch(entry);
if (shaped)
return shaped;
}
}
const anchor = await this.storage.getEpochAnchor?.();
return anchor ? normalizePromptEpoch(anchor) : undefined;
}
async appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromHook) {
const carried = await (async () => {
try {
const ctx = buildSessionContext(await this.getBranch());
return { thinkingLevel: ctx.thinkingLevel, ...(ctx.model !== null ? { model: ctx.model } : {}) };
}
catch {
return undefined;
}
})();
const definedOverrides = Object.fromEntries(Object.entries(details ?? {}).filter(([, v]) => v !== undefined));
const mergedDetails = carried === undefined
? details
: details === undefined || (typeof details === "object" && details !== null && !Array.isArray(details))
? { ...carried, ...definedOverrides }
: details;
return this.appendTypedEntry({
type: "compaction",
id: await this.storage.createEntryId(),
parentId: await this.storage.getLeafId(),
timestamp: new Date().toISOString(),
summary,
firstKeptEntryId,
tokensBefore,
details: mergedDetails,
fromHook,
});
}
async appendCustomEntry(customType, data) {
return this.appendTypedEntry({
type: "custom",
id: await this.storage.createEntryId(),
parentId: await this.storage.getLeafId(),
timestamp: new Date().toISOString(),
customType,
data,
});
}
async appendCustomMessageEntry(customType, content, display, details) {
return this.appendTypedEntry({
type: "custom_message",
id: await this.storage.createEntryId(),
parentId: await this.storage.getLeafId(),
timestamp: new Date().toISOString(),
customType,
content,
display,
details,
});
}
async appendLabel(targetId, label) {
if (!(await this.storage.getEntry(targetId))) {
throw new SessionError("not_found", `Entry ${targetId} not found`);
}
return this.appendTypedEntry({
type: "label",
id: await this.storage.createEntryId(),
parentId: await this.storage.getLeafId(),
timestamp: new Date().toISOString(),
targetId,
label,
});
}
async appendSessionName(name) {
return this.appendTypedEntry({
type: "session_info",
id: await this.storage.createEntryId(),
parentId: await this.storage.getLeafId(),
timestamp: new Date().toISOString(),
name: name.trim(),
});
}
}
export function gitFrameContextVisible(branch, entryId) {
let frameIdx = -1;
let compactionIdx = -1;
let firstKeptId;
for (let i = 0; i < branch.length; i++) {
const e = branch[i];
if (e.id === entryId)
frameIdx = i;
if (e.type === "compaction") {
compactionIdx = i;
firstKeptId = e.firstKeptEntryId;
}
}
if (frameIdx === -1)
return false;
if (compactionIdx === -1 || frameIdx > compactionIdx)
return true;
if (firstKeptId === undefined)
return false;
const keptIdx = branch.findIndex((e) => e.id === firstKeptId);
return keptIdx !== -1 && frameIdx >= keptIdx;
}