@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,550 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* messageAssemblerProcessor
|
|
3
|
+
*
|
|
4
|
+
* Pure Mealy transition function that assembles complete Message Layer events
|
|
5
|
+
* from Stream Layer events.
|
|
6
|
+
*
|
|
7
|
+
* Input Events (Stream Layer):
|
|
8
|
+
* - message_start
|
|
9
|
+
* - text_delta
|
|
10
|
+
* - tool_use_start
|
|
11
|
+
* - input_json_delta
|
|
12
|
+
* - tool_use_stop
|
|
13
|
+
* - tool_result
|
|
14
|
+
* - message_stop
|
|
15
|
+
*
|
|
16
|
+
* Output Events (Message Layer):
|
|
17
|
+
* - tool_call_message (Message - AI's request to call a tool)
|
|
18
|
+
* - tool_result_message (Message - tool execution result)
|
|
19
|
+
* - assistant_message (Message - complete assistant response)
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { Processor, ProcessorDefinition } from "../mealy";
|
|
23
|
+
import type {
|
|
24
|
+
// Input: StreamEvent (from agent layer)
|
|
25
|
+
StreamEvent,
|
|
26
|
+
MessageStartEvent,
|
|
27
|
+
TextDeltaEvent,
|
|
28
|
+
ToolUseStartEvent,
|
|
29
|
+
InputJsonDeltaEvent,
|
|
30
|
+
ToolResultEvent,
|
|
31
|
+
MessageStopEvent,
|
|
32
|
+
// Output: Message events
|
|
33
|
+
AssistantMessageEvent,
|
|
34
|
+
ToolCallMessageEvent,
|
|
35
|
+
ToolResultMessageEvent,
|
|
36
|
+
ErrorMessageEvent,
|
|
37
|
+
// Message types
|
|
38
|
+
AssistantMessage,
|
|
39
|
+
ToolCallMessage,
|
|
40
|
+
ToolResultMessage,
|
|
41
|
+
ErrorMessage,
|
|
42
|
+
// Content parts
|
|
43
|
+
TextPart,
|
|
44
|
+
ToolCallPart,
|
|
45
|
+
ToolResultPart,
|
|
46
|
+
} from "../../types";
|
|
47
|
+
|
|
48
|
+
// ===== State Types =====
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Pending content accumulator
|
|
52
|
+
*/
|
|
53
|
+
export interface PendingContent {
|
|
54
|
+
type: "text" | "tool_use";
|
|
55
|
+
index: number;
|
|
56
|
+
// For text content
|
|
57
|
+
textDeltas?: string[];
|
|
58
|
+
// For tool use
|
|
59
|
+
toolId?: string;
|
|
60
|
+
toolName?: string;
|
|
61
|
+
toolInputJson?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Pending tool call info (for matching with tool_result)
|
|
66
|
+
*/
|
|
67
|
+
export interface PendingToolCall {
|
|
68
|
+
id: string;
|
|
69
|
+
name: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* MessageAssemblerState
|
|
74
|
+
*
|
|
75
|
+
* Tracks the state of message assembly from stream events.
|
|
76
|
+
*/
|
|
77
|
+
export interface MessageAssemblerState {
|
|
78
|
+
/**
|
|
79
|
+
* Current message ID being assembled
|
|
80
|
+
*/
|
|
81
|
+
currentMessageId: string | null;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Timestamp when the current message started
|
|
85
|
+
*/
|
|
86
|
+
messageStartTime: number | null;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Pending content blocks being accumulated
|
|
90
|
+
* Key is the content block index
|
|
91
|
+
*/
|
|
92
|
+
pendingContents: Record<number, PendingContent>;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Pending tool calls waiting for results
|
|
96
|
+
* Key is the tool call ID
|
|
97
|
+
*/
|
|
98
|
+
pendingToolCalls: Record<string, PendingToolCall>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Initial state factory for MessageAssembler
|
|
103
|
+
*/
|
|
104
|
+
export function createInitialMessageAssemblerState(): MessageAssemblerState {
|
|
105
|
+
return {
|
|
106
|
+
currentMessageId: null,
|
|
107
|
+
messageStartTime: null,
|
|
108
|
+
pendingContents: {},
|
|
109
|
+
pendingToolCalls: {},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ===== Processor Implementation =====
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Generate a unique ID
|
|
117
|
+
*/
|
|
118
|
+
function generateId(): string {
|
|
119
|
+
return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Output event types from MessageAssembler
|
|
124
|
+
*/
|
|
125
|
+
export type MessageAssemblerOutput =
|
|
126
|
+
| AssistantMessageEvent
|
|
127
|
+
| ToolCallMessageEvent
|
|
128
|
+
| ToolResultMessageEvent
|
|
129
|
+
| ErrorMessageEvent;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Input event types for MessageAssembler
|
|
133
|
+
*/
|
|
134
|
+
export type MessageAssemblerInput = StreamEvent;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* messageAssemblerProcessor
|
|
138
|
+
*
|
|
139
|
+
* Pure Mealy transition function for message assembly.
|
|
140
|
+
* Pattern: (state, input) => [newState, outputs]
|
|
141
|
+
*/
|
|
142
|
+
export const messageAssemblerProcessor: Processor<
|
|
143
|
+
MessageAssemblerState,
|
|
144
|
+
MessageAssemblerInput,
|
|
145
|
+
MessageAssemblerOutput
|
|
146
|
+
> = (state, input): [MessageAssemblerState, MessageAssemblerOutput[]] => {
|
|
147
|
+
switch (input.type) {
|
|
148
|
+
case "message_start":
|
|
149
|
+
return handleMessageStart(state, input);
|
|
150
|
+
|
|
151
|
+
case "text_delta":
|
|
152
|
+
return handleTextDelta(state, input);
|
|
153
|
+
|
|
154
|
+
case "tool_use_start":
|
|
155
|
+
return handleToolUseStart(state, input);
|
|
156
|
+
|
|
157
|
+
case "input_json_delta":
|
|
158
|
+
return handleInputJsonDelta(state, input);
|
|
159
|
+
|
|
160
|
+
case "tool_use_stop":
|
|
161
|
+
return handleToolUseStop(state, input);
|
|
162
|
+
|
|
163
|
+
case "tool_result":
|
|
164
|
+
return handleToolResult(state, input);
|
|
165
|
+
|
|
166
|
+
case "message_stop":
|
|
167
|
+
return handleMessageStop(state, input);
|
|
168
|
+
|
|
169
|
+
case "error_received":
|
|
170
|
+
return handleErrorReceived(state, input);
|
|
171
|
+
|
|
172
|
+
default:
|
|
173
|
+
// Pass through unhandled events (no state change, no output)
|
|
174
|
+
return [state, []];
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Handle message_start event
|
|
180
|
+
*/
|
|
181
|
+
function handleMessageStart(
|
|
182
|
+
state: Readonly<MessageAssemblerState>,
|
|
183
|
+
event: StreamEvent
|
|
184
|
+
): [MessageAssemblerState, MessageAssemblerOutput[]] {
|
|
185
|
+
const { data } = event as MessageStartEvent;
|
|
186
|
+
return [
|
|
187
|
+
{
|
|
188
|
+
...state,
|
|
189
|
+
currentMessageId: data.messageId,
|
|
190
|
+
messageStartTime: event.timestamp,
|
|
191
|
+
pendingContents: {},
|
|
192
|
+
},
|
|
193
|
+
[],
|
|
194
|
+
];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Handle text_delta event
|
|
199
|
+
*/
|
|
200
|
+
function handleTextDelta(
|
|
201
|
+
state: Readonly<MessageAssemblerState>,
|
|
202
|
+
event: StreamEvent
|
|
203
|
+
): [MessageAssemblerState, MessageAssemblerOutput[]] {
|
|
204
|
+
const { data } = event as TextDeltaEvent;
|
|
205
|
+
const index = 0; // Text content uses index 0
|
|
206
|
+
const existingContent = state.pendingContents[index];
|
|
207
|
+
|
|
208
|
+
const pendingContent: PendingContent =
|
|
209
|
+
existingContent?.type === "text"
|
|
210
|
+
? {
|
|
211
|
+
...existingContent,
|
|
212
|
+
textDeltas: [...(existingContent.textDeltas || []), data.text],
|
|
213
|
+
}
|
|
214
|
+
: {
|
|
215
|
+
type: "text",
|
|
216
|
+
index,
|
|
217
|
+
textDeltas: [data.text],
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
return [
|
|
221
|
+
{
|
|
222
|
+
...state,
|
|
223
|
+
pendingContents: {
|
|
224
|
+
...state.pendingContents,
|
|
225
|
+
[index]: pendingContent,
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
[],
|
|
229
|
+
];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Handle tool_use_start event
|
|
234
|
+
*/
|
|
235
|
+
function handleToolUseStart(
|
|
236
|
+
state: Readonly<MessageAssemblerState>,
|
|
237
|
+
event: StreamEvent
|
|
238
|
+
): [MessageAssemblerState, MessageAssemblerOutput[]] {
|
|
239
|
+
const { data } = event as ToolUseStartEvent;
|
|
240
|
+
const index = 1; // Tool use uses index 1
|
|
241
|
+
|
|
242
|
+
const pendingContent: PendingContent = {
|
|
243
|
+
type: "tool_use",
|
|
244
|
+
index,
|
|
245
|
+
toolId: data.toolCallId,
|
|
246
|
+
toolName: data.toolName,
|
|
247
|
+
toolInputJson: "",
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
return [
|
|
251
|
+
{
|
|
252
|
+
...state,
|
|
253
|
+
pendingContents: {
|
|
254
|
+
...state.pendingContents,
|
|
255
|
+
[index]: pendingContent,
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
[],
|
|
259
|
+
];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Handle input_json_delta event
|
|
264
|
+
*/
|
|
265
|
+
function handleInputJsonDelta(
|
|
266
|
+
state: Readonly<MessageAssemblerState>,
|
|
267
|
+
event: StreamEvent
|
|
268
|
+
): [MessageAssemblerState, MessageAssemblerOutput[]] {
|
|
269
|
+
const { data } = event as InputJsonDeltaEvent;
|
|
270
|
+
const index = 1; // Tool use uses index 1
|
|
271
|
+
const existingContent = state.pendingContents[index];
|
|
272
|
+
|
|
273
|
+
if (!existingContent || existingContent.type !== "tool_use") {
|
|
274
|
+
// No pending tool_use content, ignore
|
|
275
|
+
return [state, []];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const pendingContent: PendingContent = {
|
|
279
|
+
...existingContent,
|
|
280
|
+
toolInputJson: (existingContent.toolInputJson || "") + data.partialJson,
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
return [
|
|
284
|
+
{
|
|
285
|
+
...state,
|
|
286
|
+
pendingContents: {
|
|
287
|
+
...state.pendingContents,
|
|
288
|
+
[index]: pendingContent,
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
[],
|
|
292
|
+
];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Handle tool_use_stop event
|
|
297
|
+
*
|
|
298
|
+
* Emits:
|
|
299
|
+
* - tool_call_message (Message Event) - for UI display and tool execution
|
|
300
|
+
*/
|
|
301
|
+
function handleToolUseStop(
|
|
302
|
+
state: Readonly<MessageAssemblerState>,
|
|
303
|
+
_event: StreamEvent
|
|
304
|
+
): [MessageAssemblerState, MessageAssemblerOutput[]] {
|
|
305
|
+
const index = 1;
|
|
306
|
+
const pendingContent = state.pendingContents[index];
|
|
307
|
+
|
|
308
|
+
if (!pendingContent || pendingContent.type !== "tool_use") {
|
|
309
|
+
return [state, []];
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Get tool info from pendingContent (saved during tool_use_start)
|
|
313
|
+
const toolId = pendingContent.toolId || "";
|
|
314
|
+
const toolName = pendingContent.toolName || "";
|
|
315
|
+
|
|
316
|
+
// Parse tool input JSON (accumulated during input_json_delta)
|
|
317
|
+
let toolInput: Record<string, unknown> = {};
|
|
318
|
+
try {
|
|
319
|
+
toolInput = pendingContent.toolInputJson ? JSON.parse(pendingContent.toolInputJson) : {};
|
|
320
|
+
} catch {
|
|
321
|
+
// Failed to parse, use empty object
|
|
322
|
+
toolInput = {};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Create ToolCallPart
|
|
326
|
+
const toolCall: ToolCallPart = {
|
|
327
|
+
type: "tool-call",
|
|
328
|
+
id: toolId,
|
|
329
|
+
name: toolName,
|
|
330
|
+
input: toolInput,
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// Create ToolCallMessage (complete Message object)
|
|
334
|
+
// parentId links this tool call to its parent assistant message
|
|
335
|
+
const messageId = generateId();
|
|
336
|
+
const timestamp = Date.now();
|
|
337
|
+
const toolCallMessage: ToolCallMessage = {
|
|
338
|
+
id: messageId,
|
|
339
|
+
role: "assistant",
|
|
340
|
+
subtype: "tool-call",
|
|
341
|
+
toolCall,
|
|
342
|
+
timestamp,
|
|
343
|
+
parentId: state.currentMessageId || undefined,
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// Emit tool_call_message event - data is complete Message object
|
|
347
|
+
const toolCallMessageEvent: ToolCallMessageEvent = {
|
|
348
|
+
type: "tool_call_message",
|
|
349
|
+
timestamp,
|
|
350
|
+
data: toolCallMessage,
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// Remove from pending contents, add to pending tool calls
|
|
354
|
+
const { [index]: _, ...remainingContents } = state.pendingContents;
|
|
355
|
+
|
|
356
|
+
return [
|
|
357
|
+
{
|
|
358
|
+
...state,
|
|
359
|
+
pendingContents: remainingContents,
|
|
360
|
+
pendingToolCalls: {
|
|
361
|
+
...state.pendingToolCalls,
|
|
362
|
+
[toolId]: { id: toolId, name: toolName },
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
[toolCallMessageEvent],
|
|
366
|
+
];
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Handle tool_result event
|
|
371
|
+
*
|
|
372
|
+
* Emits:
|
|
373
|
+
* - tool_result_message (Message Event) - for UI display
|
|
374
|
+
*/
|
|
375
|
+
function handleToolResult(
|
|
376
|
+
state: Readonly<MessageAssemblerState>,
|
|
377
|
+
event: StreamEvent
|
|
378
|
+
): [MessageAssemblerState, MessageAssemblerOutput[]] {
|
|
379
|
+
const { data } = event as ToolResultEvent;
|
|
380
|
+
const { toolCallId, result, isError } = data;
|
|
381
|
+
|
|
382
|
+
// Find pending tool call
|
|
383
|
+
const pendingToolCall = state.pendingToolCalls[toolCallId];
|
|
384
|
+
const toolName = pendingToolCall?.name || "unknown";
|
|
385
|
+
|
|
386
|
+
// Create tool result part
|
|
387
|
+
const toolResult: ToolResultPart = {
|
|
388
|
+
type: "tool-result",
|
|
389
|
+
id: toolCallId,
|
|
390
|
+
name: toolName,
|
|
391
|
+
output: {
|
|
392
|
+
type: isError ? "error-text" : "text",
|
|
393
|
+
value: typeof result === "string" ? result : JSON.stringify(result),
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
// Create ToolResultMessage (complete Message object)
|
|
398
|
+
const messageId = generateId();
|
|
399
|
+
const timestamp = Date.now();
|
|
400
|
+
const toolResultMessage: ToolResultMessage = {
|
|
401
|
+
id: messageId,
|
|
402
|
+
role: "tool",
|
|
403
|
+
subtype: "tool-result",
|
|
404
|
+
toolCallId,
|
|
405
|
+
toolResult,
|
|
406
|
+
timestamp,
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
// Emit tool_result_message event - data is complete Message object
|
|
410
|
+
const toolResultMessageEvent: ToolResultMessageEvent = {
|
|
411
|
+
type: "tool_result_message",
|
|
412
|
+
timestamp,
|
|
413
|
+
data: toolResultMessage,
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
// Remove from pending tool calls
|
|
417
|
+
const { [toolCallId]: _, ...remainingToolCalls } = state.pendingToolCalls;
|
|
418
|
+
|
|
419
|
+
return [
|
|
420
|
+
{
|
|
421
|
+
...state,
|
|
422
|
+
pendingToolCalls: remainingToolCalls,
|
|
423
|
+
},
|
|
424
|
+
[toolResultMessageEvent],
|
|
425
|
+
];
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Handle message_stop event
|
|
430
|
+
*/
|
|
431
|
+
function handleMessageStop(
|
|
432
|
+
state: Readonly<MessageAssemblerState>,
|
|
433
|
+
event: StreamEvent
|
|
434
|
+
): [MessageAssemblerState, MessageAssemblerOutput[]] {
|
|
435
|
+
const { data } = event as MessageStopEvent;
|
|
436
|
+
|
|
437
|
+
if (!state.currentMessageId) {
|
|
438
|
+
return [state, []];
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Assemble all text content
|
|
442
|
+
const textParts: string[] = [];
|
|
443
|
+
const sortedContents = Object.values(state.pendingContents).sort((a, b) => a.index - b.index);
|
|
444
|
+
|
|
445
|
+
for (const pending of sortedContents) {
|
|
446
|
+
if (pending.type === "text" && pending.textDeltas) {
|
|
447
|
+
textParts.push(pending.textDeltas.join(""));
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const textContent = textParts.join("");
|
|
452
|
+
|
|
453
|
+
// Skip empty messages (but preserve pendingToolCalls if stopReason is "tool_use")
|
|
454
|
+
const stopReason = data.stopReason;
|
|
455
|
+
if (!textContent || textContent.trim().length === 0) {
|
|
456
|
+
const shouldPreserveToolCalls = stopReason === "tool_use";
|
|
457
|
+
return [
|
|
458
|
+
{
|
|
459
|
+
...createInitialMessageAssemblerState(),
|
|
460
|
+
pendingToolCalls: shouldPreserveToolCalls ? state.pendingToolCalls : {},
|
|
461
|
+
},
|
|
462
|
+
[],
|
|
463
|
+
];
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Create content parts (new structure uses ContentPart[])
|
|
467
|
+
const contentParts: TextPart[] = [
|
|
468
|
+
{
|
|
469
|
+
type: "text",
|
|
470
|
+
text: textContent,
|
|
471
|
+
},
|
|
472
|
+
];
|
|
473
|
+
|
|
474
|
+
// Create AssistantMessage (complete Message object)
|
|
475
|
+
const timestamp = state.messageStartTime || Date.now();
|
|
476
|
+
const assistantMessage: AssistantMessage = {
|
|
477
|
+
id: state.currentMessageId,
|
|
478
|
+
role: "assistant",
|
|
479
|
+
subtype: "assistant",
|
|
480
|
+
content: contentParts,
|
|
481
|
+
timestamp,
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
// Emit AssistantMessageEvent - data is complete Message object
|
|
485
|
+
const assistantEvent: AssistantMessageEvent = {
|
|
486
|
+
type: "assistant_message",
|
|
487
|
+
timestamp,
|
|
488
|
+
data: assistantMessage,
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// Reset state, but preserve pendingToolCalls if stopReason is "tool_use"
|
|
492
|
+
// (tool_result events arrive after message_stop in tool call scenarios)
|
|
493
|
+
const shouldPreserveToolCalls = stopReason === "tool_use";
|
|
494
|
+
|
|
495
|
+
return [
|
|
496
|
+
{
|
|
497
|
+
...createInitialMessageAssemblerState(),
|
|
498
|
+
pendingToolCalls: shouldPreserveToolCalls ? state.pendingToolCalls : {},
|
|
499
|
+
},
|
|
500
|
+
[assistantEvent],
|
|
501
|
+
];
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Handle error_received event
|
|
506
|
+
*
|
|
507
|
+
* Emits: error_message (Message Event) - for UI display
|
|
508
|
+
*/
|
|
509
|
+
function handleErrorReceived(
|
|
510
|
+
_state: Readonly<MessageAssemblerState>,
|
|
511
|
+
event: StreamEvent
|
|
512
|
+
): [MessageAssemblerState, MessageAssemblerOutput[]] {
|
|
513
|
+
const data = event.data as { message: string; errorCode?: string };
|
|
514
|
+
|
|
515
|
+
// Create ErrorMessage (complete Message object)
|
|
516
|
+
const messageId = generateId();
|
|
517
|
+
const timestamp = Date.now();
|
|
518
|
+
const errorMessage: ErrorMessage = {
|
|
519
|
+
id: messageId,
|
|
520
|
+
role: "error",
|
|
521
|
+
subtype: "error",
|
|
522
|
+
content: data.message,
|
|
523
|
+
errorCode: data.errorCode,
|
|
524
|
+
timestamp,
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
// Emit error_message event - data is complete Message object
|
|
528
|
+
const errorMessageEvent: ErrorMessageEvent = {
|
|
529
|
+
type: "error_message",
|
|
530
|
+
timestamp,
|
|
531
|
+
data: errorMessage,
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// Reset state on error
|
|
535
|
+
return [createInitialMessageAssemblerState(), [errorMessageEvent]];
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* MessageAssembler Processor Definition
|
|
540
|
+
*/
|
|
541
|
+
export const messageAssemblerProcessorDef: ProcessorDefinition<
|
|
542
|
+
MessageAssemblerState,
|
|
543
|
+
MessageAssemblerInput,
|
|
544
|
+
MessageAssemblerOutput
|
|
545
|
+
> = {
|
|
546
|
+
name: "MessageAssembler",
|
|
547
|
+
description: "Assembles complete messages from stream events",
|
|
548
|
+
initialState: createInitialMessageAssemblerState,
|
|
549
|
+
processor: messageAssemblerProcessor,
|
|
550
|
+
};
|