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.
- package/build.ts +26 -0
- package/package.json +8 -27
- package/src/RemoteClient.ts +221 -0
- package/src/index.ts +118 -0
- package/src/presentation/Presentation.ts +189 -0
- package/src/presentation/index.ts +32 -0
- package/src/presentation/reducer.ts +317 -0
- package/src/presentation/types.ts +111 -0
- package/src/types.ts +337 -0
- package/tsconfig.json +11 -0
- package/README.md +0 -393
- package/dist/browser.js +0 -275
- package/dist/browser.js.map +0 -12
- package/dist/index.d.ts +0 -58
- package/dist/index.js +0 -481
- package/dist/index.js.map +0 -13
|
@@ -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
|
+
};
|