Something went wrong. Try again.
A local-first event pipeline for independent agents, built on Jazz.
Something went wrong. Try again.
8.1 kB · 181 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182import { z } from "zod";import { canonicalJson, sha256, type JsonObject } from "../core/json.js";import type { ThoughtEvent } from "../events/types.js";import { conversationCompactionOutputSchema, type ConversationCompactionOutput,} from "./output-contracts.js";
export const CONVERSATION_COMPACTION_EVENT_TYPE = "stream.thought.derived.conversation.compaction";export const CONVERSATION_COMPACTION_SCHEMA_VERSION = 1;
const sha256Schema = z.string().regex(/^[a-f0-9]{64}$/);
export const conversationCompactionPlanSchema = z.object({ version: z.literal(1), targetAgentId: z.string().min(1).max(200), historyAgentIds: z.array(z.string().min(1).max(200)).min(1).max(32), activationSnapshotId: z.string().min(1), activationSnapshotSha256: sha256Schema, activationSourceSequence: z.number().int().nonnegative(), conversationSource: z.string().min(1).max(200), chatId: z.string().min(1).max(100), senderId: z.string().min(1).max(100), previousBoundaryEventId: z.string().min(1).nullable(), previousBoundarySha256: sha256Schema.nullable(), previousCoveredThroughSourceSequence: z.number().int().nonnegative(), coveredThroughTurnEventId: z.string().min(1), coveredThroughSourceSequence: z.number().int().positive(), triggerInputChars: z.number().int().min(16).max(1_000_000), retainInputChars: z.number().int().positive().max(999_999), uncompactedInputChars: z.number().int().positive().max(2_000_000), retainedInputChars: z.number().int().positive().max(1_000_000), coveredSourceEvents: z.number().int().positive().max(1_000), inputTurns: z.number().int().positive().max(1_000), inputChars: z.number().int().positive().max(1_000_000), inputTurnEventIdsSha256: sha256Schema, inputContentSha256: sha256Schema,}).strict().superRefine((value, context) => { if ((value.previousBoundaryEventId === null) !== (value.previousBoundarySha256 === null)) { context.addIssue({ code: "custom", path: ["previousBoundaryEventId"], message: "Prior boundary id and hash must appear together" }); } if (value.previousBoundaryEventId === null && value.previousCoveredThroughSourceSequence !== value.activationSourceSequence) { context.addIssue({ code: "custom", path: ["previousCoveredThroughSourceSequence"], message: "First boundary must begin at its persisted activation frontier" }); } if (value.previousBoundaryEventId !== null && value.previousCoveredThroughSourceSequence < 1) { context.addIssue({ code: "custom", path: ["previousCoveredThroughSourceSequence"], message: "Recursive boundary requires positive prior coverage" }); } if (value.coveredThroughSourceSequence <= value.previousCoveredThroughSourceSequence) { context.addIssue({ code: "custom", path: ["coveredThroughSourceSequence"], message: "Compaction coverage must advance" }); } if (value.retainInputChars >= value.triggerInputChars) { context.addIssue({ code: "custom", path: ["retainInputChars"], message: "Compaction must retain fewer input characters than its trigger" }); } if (value.uncompactedInputChars < value.triggerInputChars) { context.addIssue({ code: "custom", path: ["uncompactedInputChars"], message: "Compaction input must meet the character trigger" }); } if (value.retainedInputChars > value.retainInputChars) { context.addIssue({ code: "custom", path: ["retainedInputChars"], message: "Retained exact tail exceeds its character target" }); }});
export type ConversationCompactionPlan = z.infer<typeof conversationCompactionPlanSchema>;
export const conversationCompactionActivationSchema = z.object({ version: z.literal(1), targetAgentId: z.string().min(1).max(200), compactorAgentId: z.string().min(1).max(200), compactorAgentVersion: z.number().int().positive(), compactorDeclarationFingerprint: sha256Schema, conversationSource: z.string().min(1).max(200), chatId: z.string().min(1).max(100), senderId: z.string().min(1).max(100), historyAgentIds: z.array(z.string().min(1).max(200)).min(1).max(32), triggerInputChars: z.number().int().min(16).max(1_000_000), retainInputChars: z.number().int().positive().max(999_999), activationSourceSequence: z.number().int().nonnegative(), activatedByEventId: z.string().min(1), activatedBySourceSequence: z.number().int().positive(),}).strict();
export type ConversationCompactionActivation = z.infer<typeof conversationCompactionActivationSchema>;
const outputContractIdentitySchema = z.object({ id: z.string().min(1), version: z.number().int().positive(), sha256: sha256Schema,}).strict();
const modelSchema = z.object({ provider: z.string().min(1).max(200), id: z.string().min(1).max(500), revision: z.string().min(1).max(500).optional(),}).strict();
export const conversationCompactionContextSnapshotSchema = z.object({ id: z.string().min(1), storage: z.literal("jazz-document-version"), textSha256: sha256Schema, systemTextSha256: sha256Schema, messagesSha256: sha256Schema, manifestSha256: sha256Schema,}).strict();
export const conversationCompactionEventPayloadSchema = z.object({ runId: z.string().min(1), executionKey: z.string().min(1), inputEventId: z.string().min(1), inputSourceSequence: z.number().int().positive(), compactorAgentVersion: z.number().int().positive(), compactorDeclarationFingerprint: sha256Schema, promptHash: sha256Schema, contextSnapshot: conversationCompactionContextSnapshotSchema, summary: z.string().min(1).max(2_000), confidence: z.number().min(0).max(1), outputContract: outputContractIdentitySchema, structuredOutput: conversationCompactionOutputSchema, compactionPlan: conversationCompactionPlanSchema, boundarySha256: sha256Schema, model: modelSchema.optional(),}).strict().superRefine((value, context) => { if (value.summary !== value.structuredOutput.summary) { context.addIssue({ code: "custom", path: ["summary"], message: "Boundary preview must match structured output" }); } if (value.confidence !== value.structuredOutput.confidence) { context.addIssue({ code: "custom", path: ["confidence"], message: "Boundary confidence must match structured output" }); } if (value.boundarySha256 !== conversationCompactionBoundarySha256( value.compactionPlan, value.structuredOutput, )) { context.addIssue({ code: "custom", path: ["boundarySha256"], message: "Boundary content hash does not match" }); }}) as unknown as z.ZodType<JsonObject>;
export interface ResolvedConversationCompactionBoundary { event: ThoughtEvent; plan: ConversationCompactionPlan; output: ConversationCompactionOutput; boundarySha256: string; amendment?: { content: string; sha256: string; } | undefined;}
export class ConversationCompactionNotNeeded extends Error { readonly code = "conversation-compaction-not-needed"; constructor(readonly evidence: JsonObject) { super("Conversation history has not reached the next compaction boundary"); this.name = "ConversationCompactionNotNeeded"; }}
export function conversationCompactionBoundarySha256( plan: ConversationCompactionPlan, output: ConversationCompactionOutput,): string { return sha256(canonicalJson({ compactionPlan: plan as unknown as JsonObject, structuredOutput: output as unknown as JsonObject, }));}
export function renderConversationCompactionBoundary(output: ConversationCompactionOutput): string { const sections = [ output.boundary.trim(), renderList("Open obligations", output.openLoops), renderList("Decisions", output.decisions), renderList("Exact references", output.exactReferences), renderList("Unresolved", output.unresolved), renderList("Lookup hints", output.lookupHints), ].filter(Boolean); const rendered = sections.join("\n\n"); if (rendered.length > 64_000) throw new Error("Rendered conversation compaction boundary exceeds the native message limit"); return rendered;}
function renderList(title: string, values: string[]): string { return values.length === 0 ? "" : [`## ${title}`, ...values.map((value) => `- ${value}`)].join("\n");}