Repository navigation
Expand file tree
/
Copy pathagent-observer.ts
More file actions
51 lines (49 loc) · 2.42 KB
/
Copy pathagent-observer.ts
File metadata and controls
51 lines (49 loc) · 2.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import { EventEmitter } from 'node:events';
import type { AgentDefinition, AgentState, Observation } from './adapter.ts';
export interface ConversationTurn { role: 'input' | 'output'; text: string }
export type Logger = (event: Record<string, unknown>) => void;
/** The parts of an agent definition that describe its observations. */
export type ObservedAgent = Pick<AgentDefinition, 'profile' | 'context' | 'receivedRequest' | 'toolCall'>;
// The observation store: everything seen from one agent session, in the shared
// Observation format, plus the plain conversation and the agent's state.
// Adapters subclass it to translate their own events. It emits `observation`,
// `input`, `text`, `status` and `fault`.
export class AgentObserver extends EventEmitter {
state: AgentState = 'starting';
text = '';
conversation: ConversationTurn[] = [];
observations: Observation[] = [];
readonly agent: ObservedAgent;
clean: (text: string) => string;
log: Logger;
constructor({ agent, clean = String, log = () => {} }: { agent: ObservedAgent; clean?: (text: string) => string; log?: Logger }) {
super();
this.agent = agent; this.clean = clean; this.log = log;
}
get profile() { return this.agent.profile; }
status(state: AgentState, detail: string) { this.state = state; this.emit('status', { state, detail }); }
remember(role: ConversationTurn['role'], text: string) {
const last = this.conversation.at(-1);
if (role === 'output' && last?.role === role) last.text += text;
else this.conversation.push({ role, text });
}
conversationContext() { return JSON.stringify(this.conversation); }
add(observation: Observation, emit = true) {
this.observations.push(observation);
if (emit) this.emit('observation', observation);
}
input(text: string | undefined, metadata: Record<string, unknown> = {}, emit = true) {
const safe = this.clean(text ?? ''); if (!safe) return;
this.remember('input', safe);
if (emit) this.emit('input', { text: safe, ...metadata });
}
textDelta(text: string | undefined, metadata: Record<string, unknown> = {}, emit = true) {
const safe = this.clean(text ?? ''); if (!safe) return;
this.text += safe;
this.remember('output', safe);
if (emit) this.emit('text', { text: safe, ...metadata });
}
/** One event from the agent's command hook. Returns false for another session's event. */
hook(_event: Record<string, any>): boolean { return false; }
close() {}
}