learning_ai_common_plat/packages/events/src/timeline.ts

125 lines
4.2 KiB
TypeScript

import { z } from 'zod';
import type { EcosystemEvent } from './ecosystem.js';
export const TimelineVisibilitySchema = z.enum(['private', 'org', 'shared', 'local-only']);
export const TimelineItemSchema = z.object({
itemId: z.string().min(1),
occurredAt: z.string().datetime(),
eventName: z.string().min(1),
productId: z.string().min(1),
title: z.string().min(1),
summary: z.string().nullable().optional(),
artifactRefs: z.array(z.string().min(1)),
relatedEventIds: z.array(z.string().min(1)),
actorType: z.enum(['user', 'agent', 'system', 'device']),
visibility: TimelineVisibilitySchema,
correlationId: z.string().min(1).nullable().optional(),
});
export type TimelineItem = z.infer<typeof TimelineItemSchema>;
type EventLike = Pick<
EcosystemEvent,
| 'eventId'
| 'eventName'
| 'occurredAt'
| 'productId'
| 'artifactId'
| 'actor'
| 'trace'
| 'payload'
>;
function titleForEvent(event: EventLike): string {
if (event.eventName === 'capture.transcript.created') {
return 'Transcript captured';
}
if (event.eventName === 'memory.entry.created') {
const kind = typeof event.payload.memoryKind === 'string' ? event.payload.memoryKind : 'memory';
return `${capitalize(kind)} memory proposed`;
}
if (event.eventName === 'artifact.created') {
const type =
typeof event.payload.artifactType === 'string' ? event.payload.artifactType : 'artifact';
const title =
typeof event.payload.title === 'string' && event.payload.title ? event.payload.title : null;
return title ? title : `${capitalize(type)} created`;
}
if (event.eventName === 'artifact.linked') {
const relation = typeof event.payload.relation === 'string' ? event.payload.relation : 'linked';
return `Artifact ${relation}`;
}
return event.eventName;
}
function summaryForEvent(event: EventLike): string | null {
if (event.eventName === 'capture.transcript.created') {
const duration =
typeof event.payload.durationMs === 'number'
? `${event.payload.durationMs}ms`
: 'unknown duration';
const source =
typeof event.payload.transcriptSource === 'string'
? event.payload.transcriptSource
: 'unknown source';
return `${source} transcript captured (${duration})`;
}
if (event.eventName === 'memory.entry.created') {
const sources = Array.isArray(event.payload.sourceArtifactIds)
? event.payload.sourceArtifactIds.length
: 0;
return `${sources} source artifact${sources === 1 ? '' : 's'} linked`;
}
if (event.eventName === 'artifact.created') {
const type =
typeof event.payload.artifactType === 'string' ? event.payload.artifactType : 'artifact';
const status = typeof event.payload.status === 'string' ? event.payload.status : 'created';
return `${type} status: ${status}`;
}
if (event.eventName === 'artifact.linked') {
const target =
typeof event.payload.targetArtifactId === 'string'
? event.payload.targetArtifactId
: 'unknown target';
return `Linked to ${target}`;
}
return null;
}
function relatedEventIdsForEvent(event: EventLike): string[] {
return Array.from(
new Set(
[event.trace.parentEventId, event.trace.causationId].filter(
(value): value is string => typeof value === 'string' && value.length > 0
)
)
);
}
export function buildTimelineItem(event: EventLike): TimelineItem {
return TimelineItemSchema.parse({
itemId: `timeline_${event.eventId}`,
occurredAt: event.occurredAt,
eventName: event.eventName,
productId: event.productId,
title: titleForEvent(event),
summary: summaryForEvent(event),
artifactRefs: [event.artifactId].filter((value): value is string => Boolean(value)),
relatedEventIds: relatedEventIdsForEvent(event),
actorType: event.actor.actorType,
visibility: 'private',
correlationId: event.trace.correlationId ?? null,
});
}
export function buildTimelineItems(events: EventLike[]): TimelineItem[] {
return events
.map(buildTimelineItem)
.sort((left, right) => right.occurredAt.localeCompare(left.occurredAt));
}
function capitalize(value: string): string {
return value.length === 0 ? value : `${value[0]!.toUpperCase()}${value.slice(1)}`;
}