Something went wrong. Try again.
fork of prime agent with changes i want but are not upstreamable probqbly (and some fixes)
Something went wrong. Try again.
7.2 kB · 195 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196import type { AgentMessage } from "@earendil-works/pi-agent-core";import { type AgentObserveAgentSummary, createAgentObserveMessagePreview } from "./agent-observe.js";import type { HostRequestHandler } from "./kernel/index.js";
export const AGENT_SESSIONS_HOST_PREFIX = "agent.sessions";export type AgentSessionCapability = "send" | "recent_turns" | "stop" | "restart";export type AgentSessionDelivery = "steer" | "follow_up";
export type AgentSessionSummary = AgentObserveAgentSummary & { capabilities: readonly AgentSessionCapability[];};
export interface AgentSessionRecentTurnsInput { target: string; count?: number; maxChars?: number;}
export interface AgentSessionTurnSummary { user: string; assistant: string; toolCalls: number; startedAt?: number; endedAt?: number; messageCount: number; truncated: boolean;}
export interface AgentSessionRecentTurnsResult { agent: AgentSessionSummary; turns: AgentSessionTurnSummary[]; count: number; maxChars: number; truncated: boolean;}
export interface AgentSessionSendInput { target: string; message: string; delivery: AgentSessionDelivery;}
export interface AgentSessionSendResult { operationId: string; status: "accepted" | "queued"; delivery: AgentSessionDelivery; target: { activeSessionId: string; sessionId: string; sessionName?: string; };}
export interface AgentSessionOperationResult { operationId: string; status: "accepted";}
export interface AgentSessionController { listSessions(): AgentSessionSummary[] | Promise<AgentSessionSummary[]>; getSession(target: string): AgentSessionSummary | Promise<AgentSessionSummary>; recentTurns( input: AgentSessionRecentTurnsInput, ): AgentSessionRecentTurnsResult | Promise<AgentSessionRecentTurnsResult>; send(input: AgentSessionSendInput): AgentSessionSendResult | Promise<AgentSessionSendResult>; stop(target: string): AgentSessionOperationResult | Promise<AgentSessionOperationResult>; restart(target: string): AgentSessionOperationResult | Promise<AgentSessionOperationResult>;}
export function createAgentSessionHostHandlers(controller: AgentSessionController): Record<string, HostRequestHandler> { return { "agent.sessions.list": async () => ({ sessions: await controller.listSessions() }), "agent.sessions.get": async (payload: Record<string, unknown> = {}) => ({ session: await controller.getSession(requireString(payload, "target", "agent.sessions.get target")), }), "agent.sessions.recent_turns": async (payload: Record<string, unknown> = {}) => (await controller.recentTurns({ target: requireString(payload, "target", "agent.sessions.recent_turns target"), count: normalizeOptionalInteger(payload.count, "agent.sessions.recent_turns count"), maxChars: normalizeOptionalInteger( payload.max_chars ?? payload.maxChars, "agent.sessions.recent_turns max_chars", ), })) as unknown as Record<string, unknown>, "agent.sessions.send": async (payload: Record<string, unknown> = {}) => (await controller.send({ target: requireString(payload, "target", "agent.sessions.send target"), message: requireString(payload, "message", "agent.sessions.send message"), delivery: normalizeDelivery(payload.delivery), })) as unknown as Record<string, unknown>, "agent.sessions.stop": async (payload: Record<string, unknown> = {}) => (await controller.stop(requireString(payload, "target", "agent.sessions.stop target"))) as unknown as Record< string, unknown >, "agent.sessions.restart": async (payload: Record<string, unknown> = {}) => (await controller.restart( requireString(payload, "target", "agent.sessions.restart target"), )) as unknown as Record<string, unknown>, };}
export function normalizeAgentSessionTurnCount(count: number | undefined): number { return clampInteger(count ?? 3, 1, 20, "agent.sessions.recent_turns count");}
export function normalizeAgentSessionMaxChars(maxChars: number | undefined): number { return clampInteger(maxChars ?? 6_000, 80, 12_000, "agent.sessions.recent_turns max_chars");}
export function createAgentSessionTurnSummaries( messages: AgentMessage[], count: number, maxChars: number,): { turns: AgentSessionTurnSummary[]; truncated: boolean } { const previews = messages.map((message, index) => createAgentObserveMessagePreview(message, index, maxChars)); const allTurns: MutableTurn[] = []; let current: MutableTurn | undefined;
for (const preview of previews) { if (preview.role === "user" && current?.hasUser) { current = undefined; } if (!current) { current = createMutableTurn(); allTurns.push(current); } current.messageCount += 1; if (current.startedAt === undefined && preview.timestamp !== undefined) current.startedAt = preview.timestamp; if (preview.timestamp !== undefined) current.endedAt = preview.timestamp; current.truncated ||= preview.truncated; if (preview.role === "user") { current.hasUser = true; const result = appendText(current.user, preview.text, maxChars); current.user = result.text; current.truncated ||= result.truncated; } else if (preview.role === "assistant") { const result = appendText(current.assistant, preview.text, maxChars); current.assistant = result.text; current.truncated ||= result.truncated; current.toolCalls += preview.toolCalls?.length ?? 0; } }
const selected = allTurns.slice(-count).map(({ hasUser: _hasUser, ...turn }) => turn); return { turns: selected, truncated: allTurns.length > selected.length || selected.some((turn) => turn.truncated), };}
type MutableTurn = AgentSessionTurnSummary & { hasUser: boolean };
function createMutableTurn(): MutableTurn { return { user: "", assistant: "", toolCalls: 0, messageCount: 0, truncated: false, hasUser: false, };}
function appendText(current: string, next: string, maxChars: number): { text: string; truncated: boolean } { if (!next) return { text: current, truncated: false }; const combined = current ? `${current}\n${next}` : next; return combined.length <= maxChars ? { text: combined, truncated: false } : { text: combined.slice(0, maxChars), truncated: true };}
function requireString(payload: Record<string, unknown>, key: string, label: string): string { const value = payload[key]; if (typeof value !== "string" || !value.trim()) throw new Error(`${label} must be a non-empty string`); return value.trim();}
function normalizeOptionalInteger(value: unknown, label: string): number | undefined { if (value === undefined) return undefined; if (typeof value !== "number" || !Number.isInteger(value)) throw new Error(`${label} must be an integer when provided`); return value;}
function normalizeDelivery(value: unknown): AgentSessionDelivery { if (value === "steer" || value === "follow_up") return value; throw new Error("agent.sessions.send delivery must be 'steer' or 'follow_up'");}
function clampInteger(value: number, min: number, max: number, label: string): number { if (!Number.isInteger(value)) throw new Error(`${label} must be an integer`); if (value < min || value > max) throw new Error(`${label} must be between ${min} and ${max}`); return value;}