返回 CodeWhale
model.ts
根目录 / pet / src / core / model.ts
1 /** Versioned, vendor-neutral trace and signal contracts. All internal time is ms. */
2 export const CATEGORIES = [
3 'reasoning', 'tool', 'memory', 'code', 'filesystem', 'network', 'browser',
4 'communication', 'agent', 'orchestration', 'error', 'human', 'other',
5 ] as const;
6 export type Category = typeof CATEGORIES[number];
7 export type Status = 'pending' | 'running' | 'success' | 'error' | 'unknown';
8 export type PrivacyMode = 'redact' | 'metadata' | 'retain';
9 export interface WhaleEvent {
10 observation?: import('./evidence.js').Observation;
11 schemaVersion: 1;
12 id: string; traceId: string; parentId?: string;
13 startTime: number; endTime: number; openEnded?: boolean;
14 agentId: string; agentType?: string; parentAgentId?: string;
15 category: Category; subtype?: string; name: string;
16 model?: string; provider?: string; tool?: string;
17 inputTokens?: number; outputTokens?: number; cachedTokens?: number;
18 cost?: number; costCurrency?: string; latency?: number;
19 contextTokens?: number; contextLimit?: number; retry?: number;
20 status: Status;
21 sourceId?: string; targetId?: string; targetType?: string;
22 links?: { traceId: string; spanId: string; attributes?: Record<string, unknown> }[];
23 attributes: Record<string, unknown>; payload?: unknown; raw?: unknown;
24 }
25 export interface Trace {
26 id: string; name: string; description?: string; events: WhaleEvent[];
27 duration: number; originTime?: string; source: 'demo' | 'jsonl' | 'otlp' | 'codewhale' | 'platform';
28 privacy: PrivacyMode; warnings: string[]; metadata: Record<string, unknown>;
29 }
30 export interface BinLevel {
31 binMs: number; length: number;
32 /** Channel-major arrays, index = channel * length + time bin. */
33 onsets: Float64Array; activeMs: Float64Array; outputTokens: Float64Array;
34 cost: Float64Array; errors: Float64Array; peak: Float64Array;
35 }
36 export interface SignalPyramid {
37 duration: number; channels: readonly Category[]; levels: BinLevel[];
38 /** Fixed calibration across zoom levels; sharing this locks A/B scales. */
39 calibration: Record<Metric, number>;
40 }
41 export type Metric = 'activity' | 'onsets' | 'tokens' | 'cost';
42 export type FindingKind = 'loop' | 'burst' | 'gap' | 'retry' | 'divergence' | 'spawn' | 'context' | 'boundary';
43 export interface Finding {
44 id: string; kind: FindingKind; severity: 'info' | 'warning' | 'critical';
45 title: string; detail: string; startTime: number; endTime: number;
46 agentId?: string; eventIds: string[]; evidence: Record<string, unknown>;
47 }
48 export interface Fingerprint {
49 version: 1; channels: number[]; temporal: number[]; autocorrelation: number[];
50 features: Record<string, number>; vector: number[];
51 }
52 export interface Statistics {
53 events: number; agents: number; duration: number; errors: number; retries: number;
54 inputTokens: number; outputTokens: number; cachedTokens: number;
55 cost: number; costKnown: boolean; costCount: number; tokenCount: number; outputTokenCount: number; inputTokenCount: number;
56 latencyP50: number; latencyP95: number; observedGapRatio: number;
57 }
58 export interface Analysis {
59 sessionId?: string;
60 trace: Trace; pyramid: SignalPyramid; findings: Finding[];
61 fingerprint: Fingerprint; stats: Statistics; computeMs: number;
62 }
63 export interface Filters {
64 query: string; category: string; agent: string; model: string; tool: string; status: string;
65 }
66 export const EMPTY_FILTERS: Filters = { query: '', category: '', agent: '', model: '', tool: '', status: '' };
67 export function eventMatches(e: WhaleEvent, f: Filters): boolean {
68 if (f.category && e.category !== f.category) return false;
69 if (f.agent && e.agentId !== f.agent) return false;
70 if (f.model && e.model !== f.model) return false;
71 if (f.tool && e.tool !== f.tool) return false;
72 if (f.status && e.status !== f.status) return false;
73 if (f.query) {
74 const q = f.query.toLowerCase();
75 // Search is deliberately content-aware but runs only over locally retained fields.
76 if (![e.name, e.id, e.agentId, e.model, e.tool, e.provider, e.category,
77 JSON.stringify(e.attributes), JSON.stringify(e.payload), JSON.stringify(e.raw)].filter(Boolean).join(' ').toLowerCase().includes(q)) return false;
78 }
79 return true;
80 }
81 export const durationOf = (e: WhaleEvent): number => Math.max(0, e.endTime - e.startTime);
82 /** An explicit failure receipt can arrive after a span began or ended. Keep
83 * its timestamp distinct from the operation onset in every signal view. */
84 export function errorOnsetOf(e: WhaleEvent): number {
85 const time = e.attributes['whalesong.error_onset_ms'];
86 if (time === undefined) return e.startTime;
87 if (typeof time !== 'number' || !Number.isFinite(time) || time < e.startTime)
88 throw new Error('Invalid failure observation time.');
89 return time;
90 }
91 export function stableHash(text: string, seed = 2166136261): number {
92 let h = seed;
93 for (let i = 0; i < text.length; i++) { h ^= text.charCodeAt(i); h = Math.imul(h, 16777619); }
94 return h >>> 0;
95 }
96 export function quantile(a: number[], q: number): number {
97 if (!a.length) return 0;
98 const s = [...a].sort((a, b) => a - b), x = Math.min(1, Math.max(0, q)) * (s.length - 1);
99 return s[Math.floor(x)] + (s[Math.ceil(x)] - s[Math.floor(x)]) * (x % 1);
100 }
101 export function clamp(x: number, lo: number, hi: number): number { return Math.max(lo, Math.min(hi, x)); }
102 export function formatTime(ms: number, precise = false): string {
103 const m = Math.floor(Math.max(0, ms) / 60000), s = Math.floor(Math.max(0, ms) / 1000) % 60;
104 return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}${precise ? '.' + String(Math.floor(ms % 1000)).padStart(3, '0') : ''}`;
105 }
106
106 lines TYPESCRIPT