agentxjs 1.9.0 → 1.9.2-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.
@@ -0,0 +1,317 @@
1
+ /**
2
+ * Presentation Reducer
3
+ *
4
+ * Aggregates stream events into PresentationState.
5
+ * Pure function: (state, event) => newState
6
+ */
7
+
8
+ import type { BusEvent } from "@agentxjs/core/event";
9
+ import type {
10
+ PresentationState,
11
+ AssistantConversation,
12
+ TextBlock,
13
+ ToolBlock,
14
+ Block,
15
+ } from "./types";
16
+ import { initialPresentationState } from "./types";
17
+
18
+ // ============================================================================
19
+ // Event Data Types (from stream events)
20
+ // ============================================================================
21
+
22
+ interface MessageStartData {
23
+ messageId?: string;
24
+ model?: string;
25
+ }
26
+
27
+ interface TextDeltaData {
28
+ text: string;
29
+ }
30
+
31
+ interface ToolUseStartData {
32
+ toolUseId: string;
33
+ toolName: string;
34
+ }
35
+
36
+ interface InputJsonDeltaData {
37
+ delta: string;
38
+ }
39
+
40
+ interface ToolResultData {
41
+ toolUseId: string;
42
+ result: string;
43
+ isError?: boolean;
44
+ }
45
+
46
+ interface MessageStopData {
47
+ stopReason?: string;
48
+ }
49
+
50
+ interface ErrorData {
51
+ message: string;
52
+ code?: string;
53
+ }
54
+
55
+ // ============================================================================
56
+ // Reducer
57
+ // ============================================================================
58
+
59
+ /**
60
+ * Reduce a stream event into presentation state
61
+ */
62
+ export function presentationReducer(
63
+ state: PresentationState,
64
+ event: BusEvent
65
+ ): PresentationState {
66
+ switch (event.type) {
67
+ case "message_start":
68
+ return handleMessageStart(state, event.data as MessageStartData);
69
+
70
+ case "text_delta":
71
+ return handleTextDelta(state, event.data as TextDeltaData);
72
+
73
+ case "tool_use_start":
74
+ return handleToolUseStart(state, event.data as ToolUseStartData);
75
+
76
+ case "input_json_delta":
77
+ return handleInputJsonDelta(state, event.data as InputJsonDeltaData);
78
+
79
+ case "tool_result":
80
+ return handleToolResult(state, event.data as ToolResultData);
81
+
82
+ case "message_stop":
83
+ return handleMessageStop(state, event.data as MessageStopData);
84
+
85
+ case "error":
86
+ return handleError(state, event.data as ErrorData);
87
+
88
+ default:
89
+ return state;
90
+ }
91
+ }
92
+
93
+ // ============================================================================
94
+ // Handlers
95
+ // ============================================================================
96
+
97
+ function handleMessageStart(
98
+ state: PresentationState,
99
+ _data: MessageStartData
100
+ ): PresentationState {
101
+ // Start a new streaming assistant conversation
102
+ const streaming: AssistantConversation = {
103
+ role: "assistant",
104
+ blocks: [],
105
+ isStreaming: true,
106
+ };
107
+
108
+ return {
109
+ ...state,
110
+ streaming,
111
+ status: "thinking",
112
+ };
113
+ }
114
+
115
+ function handleTextDelta(
116
+ state: PresentationState,
117
+ data: TextDeltaData
118
+ ): PresentationState {
119
+ if (!state.streaming) {
120
+ return state;
121
+ }
122
+
123
+ const blocks = [...state.streaming.blocks];
124
+ const lastBlock = blocks[blocks.length - 1];
125
+
126
+ // Append to existing TextBlock or create new one
127
+ if (lastBlock && lastBlock.type === "text") {
128
+ blocks[blocks.length - 1] = {
129
+ ...lastBlock,
130
+ content: lastBlock.content + data.text,
131
+ };
132
+ } else {
133
+ blocks.push({
134
+ type: "text",
135
+ content: data.text,
136
+ } as TextBlock);
137
+ }
138
+
139
+ return {
140
+ ...state,
141
+ streaming: {
142
+ ...state.streaming,
143
+ blocks,
144
+ },
145
+ status: "responding",
146
+ };
147
+ }
148
+
149
+ function handleToolUseStart(
150
+ state: PresentationState,
151
+ data: ToolUseStartData
152
+ ): PresentationState {
153
+ if (!state.streaming) {
154
+ return state;
155
+ }
156
+
157
+ const toolBlock: ToolBlock = {
158
+ type: "tool",
159
+ toolUseId: data.toolUseId,
160
+ toolName: data.toolName,
161
+ toolInput: {},
162
+ status: "pending",
163
+ };
164
+
165
+ return {
166
+ ...state,
167
+ streaming: {
168
+ ...state.streaming,
169
+ blocks: [...state.streaming.blocks, toolBlock],
170
+ },
171
+ status: "executing",
172
+ };
173
+ }
174
+
175
+ function handleInputJsonDelta(
176
+ state: PresentationState,
177
+ data: InputJsonDeltaData
178
+ ): PresentationState {
179
+ if (!state.streaming) {
180
+ return state;
181
+ }
182
+
183
+ const blocks = [...state.streaming.blocks];
184
+ const lastBlock = blocks[blocks.length - 1];
185
+
186
+ // Find the last tool block and update its input
187
+ if (lastBlock && lastBlock.type === "tool") {
188
+ // Accumulate JSON delta (will be parsed when complete)
189
+ const currentInput = (lastBlock as ToolBlock & { _rawInput?: string })._rawInput || "";
190
+ const newInput = currentInput + data.delta;
191
+
192
+ // Try to parse the accumulated JSON
193
+ let toolInput = lastBlock.toolInput;
194
+ try {
195
+ toolInput = JSON.parse(newInput);
196
+ } catch {
197
+ // Not yet valid JSON, keep accumulating
198
+ }
199
+
200
+ blocks[blocks.length - 1] = {
201
+ ...lastBlock,
202
+ toolInput,
203
+ _rawInput: newInput,
204
+ status: "running",
205
+ } as ToolBlock & { _rawInput?: string };
206
+
207
+ return {
208
+ ...state,
209
+ streaming: {
210
+ ...state.streaming,
211
+ blocks,
212
+ },
213
+ };
214
+ }
215
+
216
+ return state;
217
+ }
218
+
219
+ function handleToolResult(
220
+ state: PresentationState,
221
+ data: ToolResultData
222
+ ): PresentationState {
223
+ if (!state.streaming) {
224
+ return state;
225
+ }
226
+
227
+ const blocks = state.streaming.blocks.map((block): Block => {
228
+ if (block.type === "tool" && block.toolUseId === data.toolUseId) {
229
+ return {
230
+ ...block,
231
+ toolResult: data.result,
232
+ status: data.isError ? "error" : "completed",
233
+ };
234
+ }
235
+ return block;
236
+ });
237
+
238
+ return {
239
+ ...state,
240
+ streaming: {
241
+ ...state.streaming,
242
+ blocks,
243
+ },
244
+ status: "responding",
245
+ };
246
+ }
247
+
248
+ function handleMessageStop(
249
+ state: PresentationState,
250
+ _data: MessageStopData
251
+ ): PresentationState {
252
+ if (!state.streaming) {
253
+ return state;
254
+ }
255
+
256
+ // Move streaming to conversations
257
+ const completedConversation: AssistantConversation = {
258
+ ...state.streaming,
259
+ isStreaming: false,
260
+ };
261
+
262
+ return {
263
+ ...state,
264
+ conversations: [...state.conversations, completedConversation],
265
+ streaming: null,
266
+ status: "idle",
267
+ };
268
+ }
269
+
270
+ function handleError(
271
+ state: PresentationState,
272
+ data: ErrorData
273
+ ): PresentationState {
274
+ // Add error conversation
275
+ return {
276
+ ...state,
277
+ conversations: [
278
+ ...state.conversations,
279
+ {
280
+ role: "error",
281
+ message: data.message,
282
+ },
283
+ ],
284
+ streaming: null,
285
+ status: "idle",
286
+ };
287
+ }
288
+
289
+ // ============================================================================
290
+ // Helper: Add user conversation
291
+ // ============================================================================
292
+
293
+ /**
294
+ * Add a user conversation to state
295
+ */
296
+ export function addUserConversation(
297
+ state: PresentationState,
298
+ content: string
299
+ ): PresentationState {
300
+ return {
301
+ ...state,
302
+ conversations: [
303
+ ...state.conversations,
304
+ {
305
+ role: "user",
306
+ blocks: [{ type: "text", content }],
307
+ },
308
+ ],
309
+ };
310
+ }
311
+
312
+ /**
313
+ * Create initial state
314
+ */
315
+ export function createInitialState(): PresentationState {
316
+ return { ...initialPresentationState };
317
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Presentation Types
3
+ *
4
+ * UI-friendly data model aggregated from stream events.
5
+ * This implements the Presentation Model pattern.
6
+ */
7
+
8
+ // ============================================================================
9
+ // Block Types - Basic content units
10
+ // ============================================================================
11
+
12
+ /**
13
+ * Text block
14
+ */
15
+ export interface TextBlock {
16
+ type: "text";
17
+ content: string;
18
+ }
19
+
20
+ /**
21
+ * Tool block - represents a tool call and its result
22
+ */
23
+ export interface ToolBlock {
24
+ type: "tool";
25
+ toolUseId: string;
26
+ toolName: string;
27
+ toolInput: Record<string, unknown>;
28
+ toolResult?: string;
29
+ status: "pending" | "running" | "completed" | "error";
30
+ }
31
+
32
+ /**
33
+ * Image block
34
+ */
35
+ export interface ImageBlock {
36
+ type: "image";
37
+ url: string;
38
+ alt?: string;
39
+ }
40
+
41
+ /**
42
+ * All block types
43
+ */
44
+ export type Block = TextBlock | ToolBlock | ImageBlock;
45
+
46
+ // ============================================================================
47
+ // Conversation Types - A single turn in the conversation
48
+ // ============================================================================
49
+
50
+ /**
51
+ * User conversation
52
+ */
53
+ export interface UserConversation {
54
+ role: "user";
55
+ blocks: Block[];
56
+ }
57
+
58
+ /**
59
+ * Assistant conversation
60
+ */
61
+ export interface AssistantConversation {
62
+ role: "assistant";
63
+ blocks: Block[];
64
+ isStreaming: boolean;
65
+ }
66
+
67
+ /**
68
+ * Error conversation
69
+ */
70
+ export interface ErrorConversation {
71
+ role: "error";
72
+ message: string;
73
+ }
74
+
75
+ /**
76
+ * All conversation types
77
+ */
78
+ export type Conversation = UserConversation | AssistantConversation | ErrorConversation;
79
+
80
+ // ============================================================================
81
+ // Presentation State
82
+ // ============================================================================
83
+
84
+ /**
85
+ * Presentation state - the complete UI state
86
+ */
87
+ export interface PresentationState {
88
+ /**
89
+ * All completed conversations
90
+ */
91
+ conversations: Conversation[];
92
+
93
+ /**
94
+ * Current streaming conversation (null if not streaming)
95
+ */
96
+ streaming: AssistantConversation | null;
97
+
98
+ /**
99
+ * Current status
100
+ */
101
+ status: "idle" | "thinking" | "responding" | "executing";
102
+ }
103
+
104
+ /**
105
+ * Initial presentation state
106
+ */
107
+ export const initialPresentationState: PresentationState = {
108
+ conversations: [],
109
+ streaming: null,
110
+ status: "idle",
111
+ };