@optilogic/chat 1.3.3 → 1.3.5
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/dist/index.cjs +233 -184
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +132 -2
- package/dist/index.d.ts +132 -2
- package/dist/index.js +233 -185
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/agent-response/AgentResponse.tsx +63 -1
- package/src/components/agent-response/components/ActionBar.tsx +15 -0
- package/src/components/agent-response/components/ActivityIndicators.tsx +27 -1
- package/src/components/agent-response/components/MetadataRow.tsx +20 -0
- package/src/components/agent-response/hooks/useAgentResponseAccumulator.ts +6 -216
- package/src/components/agent-response/index.ts +8 -1
- package/src/components/agent-response/reducer.ts +252 -0
- package/src/components/agent-response/types.ts +8 -0
- package/src/components/user-prompt-input/UserPromptInput.tsx +3 -0
- package/src/components/user-prompt-input/types.ts +13 -0
- package/src/index.ts +5 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure reducer for agent response messages.
|
|
3
|
+
*
|
|
4
|
+
* Used by `useAgentResponseAccumulator` for live streaming, and exported so
|
|
5
|
+
* non-React callers (e.g. server-side or store-based replay of historical
|
|
6
|
+
* conversations) can rebuild the same `AgentResponseState` from a sequence of
|
|
7
|
+
* `AgentMessage` events without rendering a component.
|
|
8
|
+
*
|
|
9
|
+
* Replay note on stable IDs: when a payload omits a per-item id
|
|
10
|
+
* (`tool.id`, `thinkingStep.id`, `knowledge.id`, `memory.id`,
|
|
11
|
+
* `statusUpdate.id`), the reducer falls back to `${type}-${Date.now()}`. That
|
|
12
|
+
* is fine for live streams but will produce different React keys on each
|
|
13
|
+
* replay. Callers reconstructing `AgentMessage` events from persisted rows
|
|
14
|
+
* should populate these ids from a stable source (e.g. the supplement-row
|
|
15
|
+
* primary key) so timeline keys remain consistent across reloads.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
initialAgentResponseState,
|
|
20
|
+
type AgentResponseState,
|
|
21
|
+
type AgentMessage,
|
|
22
|
+
type ToolCall,
|
|
23
|
+
type KnowledgeItem,
|
|
24
|
+
type MemoryItem,
|
|
25
|
+
type StatusItem,
|
|
26
|
+
type ThinkingStep,
|
|
27
|
+
type PotentialResponse,
|
|
28
|
+
} from "./types";
|
|
29
|
+
import { buildTimelineEntries } from "../agent-timeline/utils";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Pure reducer: apply a single `AgentMessage` to the accumulated state.
|
|
33
|
+
*
|
|
34
|
+
* `payload.timestamp` (epoch ms), if supplied, is used for the new item's
|
|
35
|
+
* `timestamp` and any state-level timing fields this call sets
|
|
36
|
+
* (`firstMessageTime`, `thinkingStartTime`, `responseCompleteTime`). When
|
|
37
|
+
* absent, `Date.now()` is used — matching the prior live-streaming behaviour.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* const state = events.reduce(reduceAgentMessage, initialAgentResponseState);
|
|
41
|
+
*/
|
|
42
|
+
export function reduceAgentMessage(
|
|
43
|
+
prev: AgentResponseState,
|
|
44
|
+
payload: AgentMessage,
|
|
45
|
+
): AgentResponseState {
|
|
46
|
+
const now = payload.timestamp ?? Date.now();
|
|
47
|
+
|
|
48
|
+
// If we receive a non-status message while idle, transition to processing
|
|
49
|
+
let newStatus = prev.status;
|
|
50
|
+
const isFirstMessage = prev.status === "idle" && payload.type !== "status";
|
|
51
|
+
if (isFirstMessage) {
|
|
52
|
+
newStatus = "processing";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Track first message time for total time calculation
|
|
56
|
+
const firstMessageTime =
|
|
57
|
+
prev.firstMessageTime ?? (isFirstMessage ? now : null);
|
|
58
|
+
|
|
59
|
+
switch (payload.type) {
|
|
60
|
+
case "status":
|
|
61
|
+
// "Harness connected" resets to idle
|
|
62
|
+
if (
|
|
63
|
+
payload.message === "Harness connected" ||
|
|
64
|
+
payload.status === "Harness connected"
|
|
65
|
+
) {
|
|
66
|
+
return { ...initialAgentResponseState };
|
|
67
|
+
}
|
|
68
|
+
return { ...prev, status: newStatus };
|
|
69
|
+
|
|
70
|
+
case "thinking": {
|
|
71
|
+
// Check if this is a structured thinking step
|
|
72
|
+
if (payload.thinkingStep) {
|
|
73
|
+
const newStep: ThinkingStep = {
|
|
74
|
+
id: payload.thinkingStep.id || `step-${now}`,
|
|
75
|
+
label: payload.thinkingStep.label,
|
|
76
|
+
content: payload.thinkingStep.content,
|
|
77
|
+
depth: payload.thinkingStep.depth ?? payload.depth ?? 0,
|
|
78
|
+
isCollapsed: payload.thinkingStep.isCollapsed,
|
|
79
|
+
timestamp: now,
|
|
80
|
+
agentName: payload.agentName,
|
|
81
|
+
parentAgent: payload.parentAgent,
|
|
82
|
+
};
|
|
83
|
+
const thinkingStartTime = prev.thinkingStartTime ?? now;
|
|
84
|
+
const next = {
|
|
85
|
+
...prev,
|
|
86
|
+
status: newStatus,
|
|
87
|
+
thinkingSteps: [...(prev.thinkingSteps || []), newStep],
|
|
88
|
+
thinkingStartTime,
|
|
89
|
+
firstMessageTime,
|
|
90
|
+
};
|
|
91
|
+
return { ...next, timelineEntries: buildTimelineEntries(next) };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Plain text thinking — concatenate for backward compat AND
|
|
95
|
+
// push a ThinkingStep so the timeline gets individual entries.
|
|
96
|
+
const newThinking = payload.message || payload.content || "";
|
|
97
|
+
// Add line break between thinking messages
|
|
98
|
+
const separator = prev.thinking && newThinking ? "\n\n" : "";
|
|
99
|
+
// Set thinkingStartTime on first thinking message
|
|
100
|
+
const thinkingStartTime =
|
|
101
|
+
prev.thinkingStartTime ?? (newThinking ? now : null);
|
|
102
|
+
const prevSteps = prev.thinkingSteps || [];
|
|
103
|
+
const plainStep: ThinkingStep = {
|
|
104
|
+
id: `step-${prevSteps.length}`,
|
|
105
|
+
label: newThinking,
|
|
106
|
+
content: newThinking,
|
|
107
|
+
depth: payload.depth ?? 0,
|
|
108
|
+
timestamp: now,
|
|
109
|
+
agentName: payload.agentName,
|
|
110
|
+
parentAgent: payload.parentAgent,
|
|
111
|
+
};
|
|
112
|
+
const next = {
|
|
113
|
+
...prev,
|
|
114
|
+
status: newStatus,
|
|
115
|
+
thinking: prev.thinking + separator + newThinking,
|
|
116
|
+
thinkingSteps: [...prevSteps, plainStep],
|
|
117
|
+
thinkingStartTime,
|
|
118
|
+
firstMessageTime,
|
|
119
|
+
};
|
|
120
|
+
return { ...next, timelineEntries: buildTimelineEntries(next) };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
case "tool_call": {
|
|
124
|
+
// Handle both formats: { message: "ToolName" } or { tool: { id, name, arguments } }
|
|
125
|
+
const toolName = payload.message || payload.tool?.name;
|
|
126
|
+
if (toolName) {
|
|
127
|
+
const newToolCall: ToolCall = {
|
|
128
|
+
id: payload.tool?.id || `tool-${now}`,
|
|
129
|
+
name: toolName,
|
|
130
|
+
arguments: payload.tool?.arguments,
|
|
131
|
+
timestamp: now,
|
|
132
|
+
agentName: payload.agentName,
|
|
133
|
+
parentAgent: payload.parentAgent,
|
|
134
|
+
depth: payload.depth,
|
|
135
|
+
};
|
|
136
|
+
const next = {
|
|
137
|
+
...prev,
|
|
138
|
+
status: newStatus,
|
|
139
|
+
toolCalls: [...prev.toolCalls, newToolCall],
|
|
140
|
+
firstMessageTime,
|
|
141
|
+
};
|
|
142
|
+
return { ...next, timelineEntries: buildTimelineEntries(next) };
|
|
143
|
+
}
|
|
144
|
+
return { ...prev, status: newStatus, firstMessageTime };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
case "knowledge": {
|
|
148
|
+
// Handle both formats: { message: "content" } or { knowledge: { id, source, content } }
|
|
149
|
+
const knowledgeContent = payload.message || payload.knowledge?.content;
|
|
150
|
+
if (knowledgeContent) {
|
|
151
|
+
const newKnowledge: KnowledgeItem = {
|
|
152
|
+
id: payload.knowledge?.id || `knowledge-${now}`,
|
|
153
|
+
source: payload.knowledge?.source || "unknown",
|
|
154
|
+
content: knowledgeContent,
|
|
155
|
+
timestamp: now,
|
|
156
|
+
agentName: payload.agentName,
|
|
157
|
+
parentAgent: payload.parentAgent,
|
|
158
|
+
depth: payload.depth,
|
|
159
|
+
};
|
|
160
|
+
const next = {
|
|
161
|
+
...prev,
|
|
162
|
+
status: newStatus,
|
|
163
|
+
knowledge: [...prev.knowledge, newKnowledge],
|
|
164
|
+
firstMessageTime,
|
|
165
|
+
};
|
|
166
|
+
return { ...next, timelineEntries: buildTimelineEntries(next) };
|
|
167
|
+
}
|
|
168
|
+
return { ...prev, status: newStatus, firstMessageTime };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
case "memory": {
|
|
172
|
+
// Handle both formats: { message: "content" } or { memory: { id, type, content } }
|
|
173
|
+
const memoryContent = payload.message || payload.memory?.content;
|
|
174
|
+
if (memoryContent) {
|
|
175
|
+
const newMemory: MemoryItem = {
|
|
176
|
+
id: payload.memory?.id || `memory-${now}`,
|
|
177
|
+
type: payload.memory?.type || "unknown",
|
|
178
|
+
content: memoryContent,
|
|
179
|
+
timestamp: now,
|
|
180
|
+
agentName: payload.agentName,
|
|
181
|
+
parentAgent: payload.parentAgent,
|
|
182
|
+
depth: payload.depth,
|
|
183
|
+
};
|
|
184
|
+
const next = {
|
|
185
|
+
...prev,
|
|
186
|
+
status: newStatus,
|
|
187
|
+
memory: [...prev.memory, newMemory],
|
|
188
|
+
firstMessageTime,
|
|
189
|
+
};
|
|
190
|
+
return { ...next, timelineEntries: buildTimelineEntries(next) };
|
|
191
|
+
}
|
|
192
|
+
return { ...prev, status: newStatus, firstMessageTime };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
case "response":
|
|
196
|
+
return {
|
|
197
|
+
...prev,
|
|
198
|
+
status: "complete",
|
|
199
|
+
response: payload.message || payload.content || "",
|
|
200
|
+
responseCompleteTime: now,
|
|
201
|
+
firstMessageTime: prev.firstMessageTime ?? now,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
case "status_update": {
|
|
205
|
+
const statusMessage = payload.message || payload.statusUpdate?.message;
|
|
206
|
+
if (statusMessage) {
|
|
207
|
+
const newStatusItem: StatusItem = {
|
|
208
|
+
id: payload.statusUpdate?.id || `status-${now}`,
|
|
209
|
+
message: statusMessage,
|
|
210
|
+
agent: payload.statusUpdate?.agent,
|
|
211
|
+
timestamp: now,
|
|
212
|
+
agentName: payload.agentName,
|
|
213
|
+
parentAgent: payload.parentAgent,
|
|
214
|
+
depth: payload.depth,
|
|
215
|
+
};
|
|
216
|
+
const next = {
|
|
217
|
+
...prev,
|
|
218
|
+
status: newStatus,
|
|
219
|
+
statusUpdates: [...prev.statusUpdates, newStatusItem],
|
|
220
|
+
firstMessageTime,
|
|
221
|
+
};
|
|
222
|
+
return { ...next, timelineEntries: buildTimelineEntries(next) };
|
|
223
|
+
}
|
|
224
|
+
return { ...prev, status: newStatus, firstMessageTime };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
case "potential_response": {
|
|
228
|
+
const respContent = payload.message || payload.content || "";
|
|
229
|
+
if (respContent) {
|
|
230
|
+
const newResp: PotentialResponse = {
|
|
231
|
+
id: `resp-${now}`,
|
|
232
|
+
content: respContent,
|
|
233
|
+
timestamp: now,
|
|
234
|
+
agentName: payload.agentName,
|
|
235
|
+
parentAgent: payload.parentAgent,
|
|
236
|
+
depth: payload.depth,
|
|
237
|
+
};
|
|
238
|
+
const next = {
|
|
239
|
+
...prev,
|
|
240
|
+
status: newStatus,
|
|
241
|
+
potentialResponses: [...(prev.potentialResponses || []), newResp],
|
|
242
|
+
firstMessageTime,
|
|
243
|
+
};
|
|
244
|
+
return { ...next, timelineEntries: buildTimelineEntries(next) };
|
|
245
|
+
}
|
|
246
|
+
return { ...prev, status: newStatus, firstMessageTime };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
default:
|
|
250
|
+
return { ...prev, status: newStatus, firstMessageTime };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -165,6 +165,14 @@ export interface AgentMessage {
|
|
|
165
165
|
message?: string;
|
|
166
166
|
/** Alternative content field */
|
|
167
167
|
content?: string;
|
|
168
|
+
/**
|
|
169
|
+
* Optional event timestamp (epoch ms). When supplied, the reducer uses this
|
|
170
|
+
* for the item's `timestamp` and any state-level timing fields it sets
|
|
171
|
+
* (`firstMessageTime`, `thinkingStartTime`, `responseCompleteTime`) instead
|
|
172
|
+
* of `Date.now()`. Provide this when replaying historical events so durations
|
|
173
|
+
* reflect the original run rather than load time.
|
|
174
|
+
*/
|
|
175
|
+
timestamp?: number;
|
|
168
176
|
/** For status messages */
|
|
169
177
|
status?: string;
|
|
170
178
|
/** Agent name (multi-agent scenarios) */
|
|
@@ -155,6 +155,7 @@ export const UserPromptInput = React.forwardRef<
|
|
|
155
155
|
enableTags = false,
|
|
156
156
|
onTagCreate,
|
|
157
157
|
onTagDelete,
|
|
158
|
+
anchors,
|
|
158
159
|
className,
|
|
159
160
|
...props
|
|
160
161
|
},
|
|
@@ -298,6 +299,7 @@ export const UserPromptInput = React.forwardRef<
|
|
|
298
299
|
{isSubmitting && onStop ? (
|
|
299
300
|
<Tooltip content={stopTooltip} disabled={!stopTooltip}>
|
|
300
301
|
<IconButton
|
|
302
|
+
data-tour={anchors?.stopButton}
|
|
301
303
|
icon={<Square />}
|
|
302
304
|
variant="filled"
|
|
303
305
|
size="sm"
|
|
@@ -308,6 +310,7 @@ export const UserPromptInput = React.forwardRef<
|
|
|
308
310
|
</Tooltip>
|
|
309
311
|
) : (
|
|
310
312
|
<IconButton
|
|
313
|
+
data-tour={anchors?.sendButton}
|
|
311
314
|
icon={
|
|
312
315
|
isSubmitting ? (
|
|
313
316
|
<Loader2 className="animate-spin" />
|
|
@@ -42,6 +42,19 @@ export interface UserPromptInputProps
|
|
|
42
42
|
onTagCreate?: (tag: string) => void;
|
|
43
43
|
/** Callback when a tag is deleted */
|
|
44
44
|
onTagDelete?: (tag: string) => void;
|
|
45
|
+
/** Tour anchors threaded onto internal sub-elements. */
|
|
46
|
+
anchors?: UserPromptInputAnchors;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Tour anchors (`data-tour` attribute values) for elements inside the
|
|
51
|
+
* UserPromptInput that the caller cannot otherwise reach.
|
|
52
|
+
*/
|
|
53
|
+
export interface UserPromptInputAnchors {
|
|
54
|
+
/** Send button (rendered when not submitting). */
|
|
55
|
+
sendButton?: string;
|
|
56
|
+
/** Stop button (rendered while submitting if `onStop` is provided). */
|
|
57
|
+
stopButton?: string;
|
|
45
58
|
}
|
|
46
59
|
|
|
47
60
|
export interface UserPromptInputRef {
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,8 @@ export {
|
|
|
9
9
|
// Main component
|
|
10
10
|
AgentResponse,
|
|
11
11
|
type AgentResponseProps,
|
|
12
|
+
type AgentResponseClassNames,
|
|
13
|
+
type AgentResponseAnchors,
|
|
12
14
|
|
|
13
15
|
// Sub-components (for advanced customization)
|
|
14
16
|
ActivityIndicators,
|
|
@@ -48,6 +50,9 @@ export {
|
|
|
48
50
|
// Constants
|
|
49
51
|
initialAgentResponseState,
|
|
50
52
|
|
|
53
|
+
// Pure reducer (for non-React replay of AgentMessage streams)
|
|
54
|
+
reduceAgentMessage,
|
|
55
|
+
|
|
51
56
|
// Utilities
|
|
52
57
|
formatTime,
|
|
53
58
|
formatTotalTime,
|