@agentxjs/core 1.9.1-dev
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +31 -0
- package/src/agent/AgentStateMachine.ts +151 -0
- package/src/agent/README.md +296 -0
- package/src/agent/__tests__/AgentStateMachine.test.ts +346 -0
- package/src/agent/__tests__/createAgent.test.ts +728 -0
- package/src/agent/__tests__/engine/internal/messageAssemblerProcessor.test.ts +567 -0
- package/src/agent/__tests__/engine/internal/stateEventProcessor.test.ts +315 -0
- package/src/agent/__tests__/engine/internal/turnTrackerProcessor.test.ts +340 -0
- package/src/agent/__tests__/engine/mealy/Mealy.test.ts +370 -0
- package/src/agent/__tests__/engine/mealy/Store.test.ts +123 -0
- package/src/agent/__tests__/engine/mealy/combinators.test.ts +322 -0
- package/src/agent/createAgent.ts +467 -0
- package/src/agent/engine/AgentProcessor.ts +106 -0
- package/src/agent/engine/MealyMachine.ts +184 -0
- package/src/agent/engine/internal/index.ts +35 -0
- package/src/agent/engine/internal/messageAssemblerProcessor.ts +550 -0
- package/src/agent/engine/internal/stateEventProcessor.ts +313 -0
- package/src/agent/engine/internal/turnTrackerProcessor.ts +239 -0
- package/src/agent/engine/mealy/Mealy.ts +308 -0
- package/src/agent/engine/mealy/Processor.ts +70 -0
- package/src/agent/engine/mealy/Sink.ts +56 -0
- package/src/agent/engine/mealy/Source.ts +51 -0
- package/src/agent/engine/mealy/Store.ts +98 -0
- package/src/agent/engine/mealy/combinators.ts +176 -0
- package/src/agent/engine/mealy/index.ts +45 -0
- package/src/agent/index.ts +106 -0
- package/src/agent/types/engine.ts +395 -0
- package/src/agent/types/event.ts +478 -0
- package/src/agent/types/index.ts +197 -0
- package/src/agent/types/message.ts +387 -0
- package/src/common/index.ts +8 -0
- package/src/common/logger/ConsoleLogger.ts +137 -0
- package/src/common/logger/LoggerFactoryImpl.ts +123 -0
- package/src/common/logger/index.ts +26 -0
- package/src/common/logger/types.ts +98 -0
- package/src/container/Container.ts +185 -0
- package/src/container/index.ts +44 -0
- package/src/container/types.ts +71 -0
- package/src/driver/index.ts +42 -0
- package/src/driver/types.ts +363 -0
- package/src/event/EventBus.ts +260 -0
- package/src/event/README.md +237 -0
- package/src/event/__tests__/EventBus.test.ts +251 -0
- package/src/event/index.ts +46 -0
- package/src/event/types/agent.ts +512 -0
- package/src/event/types/base.ts +241 -0
- package/src/event/types/bus.ts +429 -0
- package/src/event/types/command.ts +749 -0
- package/src/event/types/container.ts +471 -0
- package/src/event/types/driver.ts +452 -0
- package/src/event/types/index.ts +26 -0
- package/src/event/types/session.ts +314 -0
- package/src/image/Image.ts +203 -0
- package/src/image/index.ts +36 -0
- package/src/image/types.ts +77 -0
- package/src/index.ts +20 -0
- package/src/mq/OffsetGenerator.ts +48 -0
- package/src/mq/README.md +166 -0
- package/src/mq/__tests__/OffsetGenerator.test.ts +121 -0
- package/src/mq/index.ts +18 -0
- package/src/mq/types.ts +172 -0
- package/src/network/RpcClient.ts +455 -0
- package/src/network/index.ts +76 -0
- package/src/network/jsonrpc.ts +336 -0
- package/src/network/protocol.ts +90 -0
- package/src/network/types.ts +284 -0
- package/src/persistence/index.ts +27 -0
- package/src/persistence/types.ts +226 -0
- package/src/runtime/AgentXRuntime.ts +501 -0
- package/src/runtime/index.ts +56 -0
- package/src/runtime/types.ts +236 -0
- package/src/session/Session.ts +71 -0
- package/src/session/index.ts +25 -0
- package/src/session/types.ts +77 -0
- package/src/workspace/index.ts +27 -0
- package/src/workspace/types.ts +131 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stateEventProcessor
|
|
3
|
+
*
|
|
4
|
+
* Stateless event transformer: Stream Events → State Events
|
|
5
|
+
*
|
|
6
|
+
* Input Events (Stream Layer):
|
|
7
|
+
* - message_start
|
|
8
|
+
* - message_stop
|
|
9
|
+
* - text_delta (triggers responding state)
|
|
10
|
+
* - tool_use_start
|
|
11
|
+
* - tool_use_stop
|
|
12
|
+
*
|
|
13
|
+
* Output Events (State Layer):
|
|
14
|
+
* - conversation_start
|
|
15
|
+
* - conversation_responding
|
|
16
|
+
* - conversation_end
|
|
17
|
+
* - tool_planned
|
|
18
|
+
* - tool_executing
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { Processor, ProcessorDefinition } from "../mealy";
|
|
22
|
+
import type {
|
|
23
|
+
// Base type
|
|
24
|
+
EngineEvent,
|
|
25
|
+
// Input: StreamEvent (from agent layer)
|
|
26
|
+
StreamEvent,
|
|
27
|
+
MessageStartEvent,
|
|
28
|
+
MessageStopEvent,
|
|
29
|
+
ToolUseStartEvent,
|
|
30
|
+
// Output: State events
|
|
31
|
+
ConversationStartEvent,
|
|
32
|
+
ConversationRespondingEvent,
|
|
33
|
+
ConversationEndEvent,
|
|
34
|
+
ConversationInterruptedEvent,
|
|
35
|
+
ToolPlannedEvent,
|
|
36
|
+
ToolExecutingEvent,
|
|
37
|
+
ErrorOccurredEvent,
|
|
38
|
+
} from "../../types";
|
|
39
|
+
import { createLogger } from "commonxjs/logger";
|
|
40
|
+
|
|
41
|
+
const logger = createLogger("engine/stateEventProcessor");
|
|
42
|
+
|
|
43
|
+
// ===== State Types =====
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* StateEventProcessorContext
|
|
47
|
+
*
|
|
48
|
+
* Minimal context needed for event transformation logic.
|
|
49
|
+
* Does NOT track agent state - only auxiliary info for decision-making.
|
|
50
|
+
*
|
|
51
|
+
* Currently empty - no context needed as all information comes from events.
|
|
52
|
+
*/
|
|
53
|
+
export interface StateEventProcessorContext {
|
|
54
|
+
// Empty - all information comes from events
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Initial context factory for StateEventProcessor
|
|
59
|
+
*/
|
|
60
|
+
export function createInitialStateEventProcessorContext(): StateEventProcessorContext {
|
|
61
|
+
return {};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ===== Processor Implementation =====
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Output event types from StateEventProcessor
|
|
68
|
+
*/
|
|
69
|
+
export type StateEventProcessorOutput =
|
|
70
|
+
| ConversationStartEvent
|
|
71
|
+
| ConversationRespondingEvent
|
|
72
|
+
| ConversationEndEvent
|
|
73
|
+
| ConversationInterruptedEvent
|
|
74
|
+
| ToolPlannedEvent
|
|
75
|
+
| ToolExecutingEvent
|
|
76
|
+
| ErrorOccurredEvent;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Input event types for StateEventProcessor
|
|
80
|
+
*/
|
|
81
|
+
export type StateEventProcessorInput = StreamEvent;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* stateEventProcessor
|
|
85
|
+
*
|
|
86
|
+
* Stateless event transformer: Stream Events → State Events
|
|
87
|
+
*
|
|
88
|
+
* Design:
|
|
89
|
+
* - Does NOT track agent state (that's StateMachine's job)
|
|
90
|
+
* - Only maintains auxiliary context (timestamps, etc.)
|
|
91
|
+
* - Emits State Events that StateMachine consumes
|
|
92
|
+
*
|
|
93
|
+
* Pattern: (context, input) => [newContext, outputs]
|
|
94
|
+
*/
|
|
95
|
+
export const stateEventProcessor: Processor<
|
|
96
|
+
StateEventProcessorContext,
|
|
97
|
+
StateEventProcessorInput,
|
|
98
|
+
StateEventProcessorOutput
|
|
99
|
+
> = (context, input): [StateEventProcessorContext, StateEventProcessorOutput[]] => {
|
|
100
|
+
// Log all incoming Stream Events
|
|
101
|
+
logger.debug(`[Stream Event] ${input.type}`, {
|
|
102
|
+
context,
|
|
103
|
+
eventData: "data" in input ? input.data : undefined,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
switch (input.type) {
|
|
107
|
+
case "message_start":
|
|
108
|
+
return handleMessageStart(context, input);
|
|
109
|
+
|
|
110
|
+
case "message_delta":
|
|
111
|
+
return handleMessageDelta(context);
|
|
112
|
+
|
|
113
|
+
case "message_stop":
|
|
114
|
+
return handleMessageStop(context, input);
|
|
115
|
+
|
|
116
|
+
case "text_delta":
|
|
117
|
+
return handleTextDelta(context);
|
|
118
|
+
|
|
119
|
+
case "tool_use_start":
|
|
120
|
+
return handleToolUseStart(context, input);
|
|
121
|
+
|
|
122
|
+
case "tool_use_stop":
|
|
123
|
+
return handleToolUseStop(context);
|
|
124
|
+
|
|
125
|
+
case "error_received":
|
|
126
|
+
return handleErrorReceived(context, input);
|
|
127
|
+
|
|
128
|
+
default:
|
|
129
|
+
// Pass through unhandled events
|
|
130
|
+
logger.debug(`[Stream Event] ${input.type} (unhandled)`);
|
|
131
|
+
return [context, []];
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Handle message_start event
|
|
137
|
+
*
|
|
138
|
+
* Emits: conversation_start
|
|
139
|
+
*/
|
|
140
|
+
function handleMessageStart(
|
|
141
|
+
context: Readonly<StateEventProcessorContext>,
|
|
142
|
+
event: StreamEvent
|
|
143
|
+
): [StateEventProcessorContext, StateEventProcessorOutput[]] {
|
|
144
|
+
const { data } = event as MessageStartEvent;
|
|
145
|
+
|
|
146
|
+
const conversationStartEvent: ConversationStartEvent = {
|
|
147
|
+
type: "conversation_start",
|
|
148
|
+
timestamp: Date.now(),
|
|
149
|
+
data: {
|
|
150
|
+
messageId: data.messageId,
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
return [context, [conversationStartEvent]];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Handle message_delta event
|
|
159
|
+
*
|
|
160
|
+
* No longer needed as stopReason is now in message_stop event.
|
|
161
|
+
* Kept for compatibility with event routing.
|
|
162
|
+
*/
|
|
163
|
+
function handleMessageDelta(
|
|
164
|
+
context: Readonly<StateEventProcessorContext>
|
|
165
|
+
): [StateEventProcessorContext, StateEventProcessorOutput[]] {
|
|
166
|
+
// No-op: stopReason now comes from message_stop
|
|
167
|
+
return [context, []];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Handle message_stop event
|
|
172
|
+
*
|
|
173
|
+
* Emits: conversation_end (only if stopReason is NOT "tool_use")
|
|
174
|
+
*
|
|
175
|
+
* This event signals that Claude has finished streaming a message.
|
|
176
|
+
* However, if stopReason is "tool_use", the conversation continues
|
|
177
|
+
* because Claude will execute tools and send more messages.
|
|
178
|
+
*/
|
|
179
|
+
function handleMessageStop(
|
|
180
|
+
context: Readonly<StateEventProcessorContext>,
|
|
181
|
+
event: StreamEvent
|
|
182
|
+
): [StateEventProcessorContext, StateEventProcessorOutput[]] {
|
|
183
|
+
const { data } = event as MessageStopEvent;
|
|
184
|
+
const stopReason = data.stopReason;
|
|
185
|
+
|
|
186
|
+
logger.debug("message_stop received", { stopReason });
|
|
187
|
+
|
|
188
|
+
// If stopReason is "tool_use", don't emit conversation_end
|
|
189
|
+
// The conversation continues after tool execution
|
|
190
|
+
if (stopReason === "tool_use") {
|
|
191
|
+
logger.debug("Skipping conversation_end (tool_use in progress)");
|
|
192
|
+
return [context, []];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// For all other cases (end_turn, max_tokens, etc.), emit conversation_end
|
|
196
|
+
const conversationEndEvent: ConversationEndEvent = {
|
|
197
|
+
type: "conversation_end",
|
|
198
|
+
timestamp: Date.now(),
|
|
199
|
+
data: {
|
|
200
|
+
reason: "completed",
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
return [context, [conversationEndEvent]];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Handle text_delta event
|
|
209
|
+
*
|
|
210
|
+
* Emits: conversation_responding
|
|
211
|
+
*/
|
|
212
|
+
function handleTextDelta(
|
|
213
|
+
context: Readonly<StateEventProcessorContext>
|
|
214
|
+
): [StateEventProcessorContext, StateEventProcessorOutput[]] {
|
|
215
|
+
const respondingEvent: ConversationRespondingEvent = {
|
|
216
|
+
type: "conversation_responding",
|
|
217
|
+
timestamp: Date.now(),
|
|
218
|
+
data: {},
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
return [context, [respondingEvent]];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Handle tool_use_start event
|
|
226
|
+
*
|
|
227
|
+
* Emits: tool_planned, tool_executing
|
|
228
|
+
*/
|
|
229
|
+
function handleToolUseStart(
|
|
230
|
+
context: Readonly<StateEventProcessorContext>,
|
|
231
|
+
event: StreamEvent
|
|
232
|
+
): [StateEventProcessorContext, StateEventProcessorOutput[]] {
|
|
233
|
+
const { data } = event as ToolUseStartEvent;
|
|
234
|
+
const outputs: StateEventProcessorOutput[] = [];
|
|
235
|
+
|
|
236
|
+
// Emit ToolPlannedEvent
|
|
237
|
+
const toolPlannedEvent: ToolPlannedEvent = {
|
|
238
|
+
type: "tool_planned",
|
|
239
|
+
timestamp: Date.now(),
|
|
240
|
+
data: {
|
|
241
|
+
toolId: data.toolCallId,
|
|
242
|
+
toolName: data.toolName,
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
outputs.push(toolPlannedEvent);
|
|
246
|
+
|
|
247
|
+
// Emit ToolExecutingEvent
|
|
248
|
+
const toolExecutingEvent: ToolExecutingEvent = {
|
|
249
|
+
type: "tool_executing",
|
|
250
|
+
timestamp: Date.now(),
|
|
251
|
+
data: {
|
|
252
|
+
toolId: data.toolCallId,
|
|
253
|
+
toolName: data.toolName,
|
|
254
|
+
input: {},
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
outputs.push(toolExecutingEvent);
|
|
258
|
+
|
|
259
|
+
return [context, outputs];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Handle tool_use_stop event
|
|
264
|
+
*
|
|
265
|
+
* Pass through - no State Event emitted.
|
|
266
|
+
* StateMachine handles the state transition internally.
|
|
267
|
+
*/
|
|
268
|
+
function handleToolUseStop(
|
|
269
|
+
context: Readonly<StateEventProcessorContext>
|
|
270
|
+
): [StateEventProcessorContext, StateEventProcessorOutput[]] {
|
|
271
|
+
// Pass through - no State Event
|
|
272
|
+
return [context, []];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Handle error_received event
|
|
277
|
+
*
|
|
278
|
+
* Emits: error_occurred
|
|
279
|
+
*/
|
|
280
|
+
function handleErrorReceived(
|
|
281
|
+
context: Readonly<StateEventProcessorContext>,
|
|
282
|
+
event: StreamEvent
|
|
283
|
+
): [StateEventProcessorContext, StateEventProcessorOutput[]] {
|
|
284
|
+
const { data } = event as EngineEvent<"error_received", { message: string; errorCode?: string }>;
|
|
285
|
+
|
|
286
|
+
const errorOccurredEvent: ErrorOccurredEvent = {
|
|
287
|
+
type: "error_occurred",
|
|
288
|
+
timestamp: Date.now(),
|
|
289
|
+
data: {
|
|
290
|
+
code: data.errorCode || "unknown_error",
|
|
291
|
+
message: data.message,
|
|
292
|
+
recoverable: true,
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
return [context, [errorOccurredEvent]];
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* StateEvent Processor Definition
|
|
301
|
+
*
|
|
302
|
+
* Stateless event transformer: Stream Events → State Events
|
|
303
|
+
*/
|
|
304
|
+
export const stateEventProcessorDef: ProcessorDefinition<
|
|
305
|
+
StateEventProcessorContext,
|
|
306
|
+
StateEventProcessorInput,
|
|
307
|
+
StateEventProcessorOutput
|
|
308
|
+
> = {
|
|
309
|
+
name: "StateEventProcessor",
|
|
310
|
+
description: "Transform Stream Events into State Events",
|
|
311
|
+
initialState: createInitialStateEventProcessorContext,
|
|
312
|
+
processor: stateEventProcessor,
|
|
313
|
+
};
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* turnTrackerProcessor
|
|
3
|
+
*
|
|
4
|
+
* Pure Mealy transition function that tracks request-response turn pairs.
|
|
5
|
+
*
|
|
6
|
+
* Input Events:
|
|
7
|
+
* - user_message (Message Layer)
|
|
8
|
+
* - message_stop (Stream Layer - contains stop reason)
|
|
9
|
+
* - assistant_message (Message Layer)
|
|
10
|
+
*
|
|
11
|
+
* Output Events (Turn Layer):
|
|
12
|
+
* - turn_request
|
|
13
|
+
* - turn_response
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { Processor, ProcessorDefinition } from "../mealy";
|
|
17
|
+
import type {
|
|
18
|
+
// Input: combined stream and message events
|
|
19
|
+
StreamEvent,
|
|
20
|
+
AgentMessageEvent,
|
|
21
|
+
MessageStopEvent,
|
|
22
|
+
UserMessageEvent,
|
|
23
|
+
// Output: Turn events
|
|
24
|
+
TurnRequestEvent,
|
|
25
|
+
TurnResponseEvent,
|
|
26
|
+
// Data types
|
|
27
|
+
TokenUsage,
|
|
28
|
+
} from "../../types";
|
|
29
|
+
|
|
30
|
+
// ===== State Types =====
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Pending turn tracking
|
|
34
|
+
*/
|
|
35
|
+
export interface PendingTurn {
|
|
36
|
+
turnId: string;
|
|
37
|
+
messageId: string;
|
|
38
|
+
content: string;
|
|
39
|
+
requestedAt: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* TurnTrackerState
|
|
44
|
+
*
|
|
45
|
+
* Tracks the current turn state.
|
|
46
|
+
*/
|
|
47
|
+
export interface TurnTrackerState {
|
|
48
|
+
/**
|
|
49
|
+
* Currently pending turn (waiting for response)
|
|
50
|
+
*/
|
|
51
|
+
pendingTurn: PendingTurn | null;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Cost per input token (USD)
|
|
55
|
+
*/
|
|
56
|
+
costPerInputToken: number;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Cost per output token (USD)
|
|
60
|
+
*/
|
|
61
|
+
costPerOutputToken: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Initial state factory for TurnTracker
|
|
66
|
+
*/
|
|
67
|
+
export function createInitialTurnTrackerState(): TurnTrackerState {
|
|
68
|
+
return {
|
|
69
|
+
pendingTurn: null,
|
|
70
|
+
costPerInputToken: 0.000003, // $3 per 1M tokens
|
|
71
|
+
costPerOutputToken: 0.000015, // $15 per 1M tokens
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ===== Processor Implementation =====
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Generate a unique ID
|
|
79
|
+
*/
|
|
80
|
+
function generateId(): string {
|
|
81
|
+
return `turn_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Output event types from TurnTracker
|
|
86
|
+
*/
|
|
87
|
+
export type TurnTrackerOutput = TurnRequestEvent | TurnResponseEvent;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Input event types for TurnTracker
|
|
91
|
+
* Accepts both Stream and Message layer events
|
|
92
|
+
*/
|
|
93
|
+
export type TurnTrackerInput = StreamEvent | AgentMessageEvent;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* turnTrackerProcessor
|
|
97
|
+
*
|
|
98
|
+
* Pure Mealy transition function for turn tracking.
|
|
99
|
+
* Pattern: (state, input) => [newState, outputs]
|
|
100
|
+
*/
|
|
101
|
+
export const turnTrackerProcessor: Processor<
|
|
102
|
+
TurnTrackerState,
|
|
103
|
+
TurnTrackerInput,
|
|
104
|
+
TurnTrackerOutput
|
|
105
|
+
> = (state, input): [TurnTrackerState, TurnTrackerOutput[]] => {
|
|
106
|
+
switch (input.type) {
|
|
107
|
+
case "user_message":
|
|
108
|
+
return handleUserMessage(state, input as AgentMessageEvent);
|
|
109
|
+
|
|
110
|
+
case "message_stop":
|
|
111
|
+
return handleMessageStop(state, input as StreamEvent);
|
|
112
|
+
|
|
113
|
+
case "assistant_message":
|
|
114
|
+
// Turn completion is handled in message_stop
|
|
115
|
+
// This handler is kept for potential future use
|
|
116
|
+
return [state, []];
|
|
117
|
+
|
|
118
|
+
default:
|
|
119
|
+
return [state, []];
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Handle user_message event
|
|
125
|
+
*/
|
|
126
|
+
function handleUserMessage(
|
|
127
|
+
state: Readonly<TurnTrackerState>,
|
|
128
|
+
event: AgentMessageEvent
|
|
129
|
+
): [TurnTrackerState, TurnTrackerOutput[]] {
|
|
130
|
+
const { data } = event as UserMessageEvent;
|
|
131
|
+
const turnId = generateId();
|
|
132
|
+
|
|
133
|
+
// Extract content as string (UserMessage.content can be string or array)
|
|
134
|
+
const contentText = typeof data.content === "string" ? data.content : "";
|
|
135
|
+
|
|
136
|
+
const pendingTurn: PendingTurn = {
|
|
137
|
+
turnId,
|
|
138
|
+
messageId: data.id,
|
|
139
|
+
content: contentText,
|
|
140
|
+
requestedAt: event.timestamp,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const turnRequestEvent: TurnRequestEvent = {
|
|
144
|
+
type: "turn_request",
|
|
145
|
+
timestamp: Date.now(),
|
|
146
|
+
data: {
|
|
147
|
+
turnId,
|
|
148
|
+
messageId: data.id,
|
|
149
|
+
content: contentText,
|
|
150
|
+
timestamp: event.timestamp,
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
return [
|
|
155
|
+
{
|
|
156
|
+
...state,
|
|
157
|
+
pendingTurn,
|
|
158
|
+
},
|
|
159
|
+
[turnRequestEvent],
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Handle message_stop event
|
|
165
|
+
*/
|
|
166
|
+
function handleMessageStop(
|
|
167
|
+
state: Readonly<TurnTrackerState>,
|
|
168
|
+
event: StreamEvent
|
|
169
|
+
): [TurnTrackerState, TurnTrackerOutput[]] {
|
|
170
|
+
if (!state.pendingTurn) {
|
|
171
|
+
return [state, []];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const { data } = event as MessageStopEvent;
|
|
175
|
+
const stopReason = data.stopReason;
|
|
176
|
+
|
|
177
|
+
// Complete turn based on stop reason
|
|
178
|
+
// - "end_turn": Normal completion (no tool use)
|
|
179
|
+
// - "tool_use": Tool calling in progress, DON'T complete yet
|
|
180
|
+
// - "max_tokens": Hit token limit, complete turn
|
|
181
|
+
// - "stop_sequence": Hit stop sequence, complete turn
|
|
182
|
+
if (stopReason === "end_turn" || stopReason === "max_tokens" || stopReason === "stop_sequence") {
|
|
183
|
+
return completeTurn(state, event.timestamp);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// For tool_use, don't complete turn yet
|
|
187
|
+
return [state, []];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Complete the turn and emit TurnResponseEvent
|
|
192
|
+
*/
|
|
193
|
+
function completeTurn(
|
|
194
|
+
state: Readonly<TurnTrackerState>,
|
|
195
|
+
completedAt: number
|
|
196
|
+
): [TurnTrackerState, TurnTrackerOutput[]] {
|
|
197
|
+
if (!state.pendingTurn) {
|
|
198
|
+
return [state, []];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const { turnId, messageId, requestedAt } = state.pendingTurn;
|
|
202
|
+
const duration = completedAt - requestedAt;
|
|
203
|
+
|
|
204
|
+
const usage: TokenUsage = { inputTokens: 0, outputTokens: 0 };
|
|
205
|
+
|
|
206
|
+
const turnResponseEvent: TurnResponseEvent = {
|
|
207
|
+
type: "turn_response",
|
|
208
|
+
timestamp: Date.now(),
|
|
209
|
+
data: {
|
|
210
|
+
turnId,
|
|
211
|
+
messageId,
|
|
212
|
+
duration,
|
|
213
|
+
usage,
|
|
214
|
+
timestamp: completedAt,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
return [
|
|
219
|
+
{
|
|
220
|
+
...state,
|
|
221
|
+
pendingTurn: null,
|
|
222
|
+
},
|
|
223
|
+
[turnResponseEvent],
|
|
224
|
+
];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* TurnTracker Processor Definition
|
|
229
|
+
*/
|
|
230
|
+
export const turnTrackerProcessorDef: ProcessorDefinition<
|
|
231
|
+
TurnTrackerState,
|
|
232
|
+
TurnTrackerInput,
|
|
233
|
+
TurnTrackerOutput
|
|
234
|
+
> = {
|
|
235
|
+
name: "TurnTracker",
|
|
236
|
+
description: "Tracks request-response turn pairs",
|
|
237
|
+
initialState: createInitialTurnTrackerState,
|
|
238
|
+
processor: turnTrackerProcessor,
|
|
239
|
+
};
|