Skip to content

Commit 5f6c432

Browse files
authored
Merge pull request #90 from UiPath/feat/agent-harness-redesign
feat: redesign agent harness with modular architecture + Responses API
2 parents 295e4d5 + cb96415 commit 5f6c432

23 files changed

Lines changed: 2124 additions & 947 deletions

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-dev"
3-
version = "0.0.63"
3+
version = "0.0.64"
44
description = "UiPath Developer Console"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

src/uipath/dev/server/__init__.py

Lines changed: 46 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from uipath.dev.models.eval_data import EvalItemResult, EvalRunState
2828
from uipath.dev.models.execution import ExecutionRun
2929
from uipath.dev.server.debug_bridge import WebDebugBridge
30-
from uipath.dev.services.agent_service import AgentService
30+
from uipath.dev.services.agent import AgentService
3131
from uipath.dev.services.eval_service import EvalService
3232
from uipath.dev.services.run_service import RunService
3333
from uipath.dev.services.skill_service import SkillService
@@ -102,13 +102,7 @@ def __init__(
102102

103103
self.agent_service = AgentService(
104104
skill_service=self.skill_service,
105-
on_status=self._on_agent_status,
106-
on_text=self._on_agent_text,
107-
on_plan=self._on_agent_plan,
108-
on_tool_use=self._on_agent_tool_use,
109-
on_tool_result=self._on_agent_tool_result,
110-
on_tool_approval=self._on_agent_tool_approval,
111-
on_error=self._on_agent_error,
105+
on_event=self._on_agent_event,
112106
)
113107

114108
def create_app(self) -> Any:
@@ -291,47 +285,52 @@ def _on_eval_run_completed(self, run: EvalRunState) -> None:
291285
"""Broadcast eval run completed to all connected clients."""
292286
self.connection_manager.broadcast_eval_run_completed(run)
293287

294-
def _on_agent_status(self, session_id: str, status: str) -> None:
295-
"""Broadcast agent status to all connected clients."""
296-
self.connection_manager.broadcast_agent_status(session_id, status)
297-
298-
def _on_agent_text(self, session_id: str, content: str, done: bool) -> None:
299-
"""Broadcast agent text to all connected clients."""
300-
self.connection_manager.broadcast_agent_text(session_id, content, done)
301-
302-
def _on_agent_plan(self, session_id: str, items: list[dict[str, str]]) -> None:
303-
"""Broadcast agent plan to all connected clients."""
304-
self.connection_manager.broadcast_agent_plan(session_id, items)
305-
306-
def _on_agent_tool_use(
307-
self, session_id: str, tool: str, args: dict[str, Any]
308-
) -> None:
309-
"""Broadcast agent tool use to all connected clients."""
310-
self.connection_manager.broadcast_agent_tool_use(session_id, tool, args)
311-
312-
def _on_agent_tool_result(
313-
self, session_id: str, tool: str, result: str, is_error: bool
314-
) -> None:
315-
"""Broadcast agent tool result to all connected clients."""
316-
self.connection_manager.broadcast_agent_tool_result(
317-
session_id, tool, result, is_error
318-
)
319-
320-
def _on_agent_tool_approval(
321-
self,
322-
session_id: str,
323-
tool_call_id: str,
324-
tool: str,
325-
args: dict[str, Any],
326-
) -> None:
327-
"""Broadcast agent tool approval request to all connected clients."""
328-
self.connection_manager.broadcast_agent_tool_approval(
329-
session_id, tool_call_id, tool, args
288+
def _on_agent_event(self, event: Any) -> None:
289+
"""Route agent events to the appropriate broadcast method."""
290+
from uipath.dev.services.agent import (
291+
ErrorOccurred,
292+
PlanUpdated,
293+
StatusChanged,
294+
TextDelta,
295+
TextGenerated,
296+
ThinkingGenerated,
297+
TokenUsageUpdated,
298+
ToolApprovalRequired,
299+
ToolCompleted,
300+
ToolStarted,
330301
)
331302

332-
def _on_agent_error(self, session_id: str, message: str) -> None:
333-
"""Broadcast agent error to all connected clients."""
334-
self.connection_manager.broadcast_agent_error(session_id, message)
303+
cm = self.connection_manager
304+
match event:
305+
case StatusChanged(session_id=sid, status=status):
306+
cm.broadcast_agent_status(sid, status)
307+
case TextGenerated(session_id=sid, content=content, done=done):
308+
cm.broadcast_agent_text(sid, content, done)
309+
case TextDelta(session_id=sid, delta=delta):
310+
cm.broadcast_agent_text_delta(sid, delta)
311+
case ThinkingGenerated(session_id=sid, content=content):
312+
cm.broadcast_agent_thinking(sid, content)
313+
case PlanUpdated(session_id=sid, items=items):
314+
cm.broadcast_agent_plan(sid, items)
315+
case ToolStarted(session_id=sid, tool=tool, args=args):
316+
cm.broadcast_agent_tool_use(sid, tool, args)
317+
case ToolCompleted(
318+
session_id=sid, tool=tool, result=result, is_error=is_error
319+
):
320+
cm.broadcast_agent_tool_result(sid, tool, result, is_error)
321+
case ToolApprovalRequired(
322+
session_id=sid, tool_call_id=tcid, tool=tool, args=args
323+
):
324+
cm.broadcast_agent_tool_approval(sid, tcid, tool, args)
325+
case ErrorOccurred(session_id=sid, message=message):
326+
cm.broadcast_agent_error(sid, message)
327+
case TokenUsageUpdated(
328+
session_id=sid,
329+
prompt_tokens=pt,
330+
completion_tokens=ct,
331+
total_session_tokens=total,
332+
):
333+
cm.broadcast_agent_token_usage(sid, pt, ct, total)
335334

336335
@staticmethod
337336
def _find_free_port(host: str, start_port: int, max_attempts: int = 100) -> int:

src/uipath/dev/server/frontend/src/components/agent/AgentChatSidebar.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ export default function AgentChatSidebar() {
8585
});
8686

8787
const isBusy = status === "thinking" || status === "executing" || status === "planning";
88+
const lastMsg = messages[messages.length - 1];
89+
const isStreaming = isBusy && lastMsg?.role === "assistant" && !lastMsg.done;
90+
const showBusyIndicator = isBusy && !isStreaming;
8891

8992
const textareaRef = useRef<HTMLTextAreaElement>(null);
9093

@@ -191,7 +194,7 @@ export default function AgentChatSidebar() {
191194
{messages.map((msg) => (
192195
<AgentMessageComponent key={msg.id} message={msg} />
193196
))}
194-
{isBusy && (
197+
{showBusyIndicator && (
195198
<div className="py-1.5">
196199
<div className="flex items-center gap-1.5">
197200
<div className="w-2 h-2 rounded-full animate-pulse" style={{ background: "var(--success)" }} />

src/uipath/dev/server/frontend/src/components/agent/AgentMessage.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,39 @@ const ROLE_CONFIG: Record<string, { label: string; color: string }> = {
1515
assistant: { label: "AI", color: "var(--success)" },
1616
tool: { label: "Tool", color: "var(--warning)" },
1717
plan: { label: "Plan", color: "var(--accent)" },
18+
thinking: { label: "Reasoning", color: "var(--text-muted)" },
1819
};
1920

21+
function ThinkingCard({ message }: Props) {
22+
const [expanded, setExpanded] = useState(false);
23+
return (
24+
<div className="py-1.5">
25+
<button
26+
onClick={() => setExpanded(!expanded)}
27+
className="flex items-center gap-1.5 mb-0.5 cursor-pointer"
28+
style={{ background: "none", border: "none", padding: 0 }}
29+
>
30+
<div className="w-2 h-2 rounded-full" style={{ background: "var(--text-muted)", opacity: 0.5 }} />
31+
<span className="text-[11px] font-semibold" style={{ color: "var(--text-muted)" }}>Reasoning</span>
32+
<svg
33+
width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="var(--text-muted)" strokeWidth="2"
34+
style={{ transform: expanded ? "rotate(180deg)" : "rotate(0deg)", transition: "transform 0.15s", marginLeft: 2 }}
35+
>
36+
<path d="M6 9l6 6 6-6" />
37+
</svg>
38+
</button>
39+
{expanded && (
40+
<div
41+
className="text-[12px] leading-relaxed pl-2.5 max-w-prose whitespace-pre-wrap"
42+
style={{ color: "var(--text-muted)", fontStyle: "italic" }}
43+
>
44+
{message.content}
45+
</div>
46+
)}
47+
</div>
48+
);
49+
}
50+
2051
function PlanCard({ message }: Props) {
2152
const items = message.planItems ?? [];
2253
return (
@@ -234,6 +265,7 @@ function ToolCard({ message }: Props) {
234265
}
235266

236267
export default function AgentMessageComponent({ message }: Props) {
268+
if (message.role === "thinking") return <ThinkingCard message={message} />;
237269
if (message.role === "plan") return <PlanCard message={message} />;
238270
if (message.role === "tool") return <ToolCard message={message} />;
239271

src/uipath/dev/server/frontend/src/store/useAgentStore.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ interface AgentStore {
2626
addToolResult: (tool: string, result: string, isError: boolean) => void;
2727
addToolApprovalRequest: (toolCallId: string, tool: string, args: Record<string, unknown>) => void;
2828
resolveToolApproval: (toolCallId: string, approved: boolean) => void;
29+
appendThinking: (content: string) => void;
2930
addError: (message: string) => void;
3031
setSessionId: (id: string) => void;
3132
setModels: (models: AgentModel[]) => void;
@@ -188,6 +189,24 @@ export const useAgentStore = create<AgentStore>((set) => ({
188189
return { messages: msgs };
189190
}),
190191

192+
appendThinking: (content) =>
193+
set((state) => {
194+
const msgs = [...state.messages];
195+
const last = msgs[msgs.length - 1];
196+
if (last && last.role === "thinking") {
197+
// Append to existing thinking message
198+
msgs[msgs.length - 1] = { ...last, content: last.content + content };
199+
} else {
200+
msgs.push({
201+
id: nextId(),
202+
role: "thinking",
203+
content,
204+
timestamp: Date.now(),
205+
});
206+
}
207+
return { messages: msgs };
208+
}),
209+
191210
addError: (message) =>
192211
set((state) => ({
193212
status: "error" as AgentStatus,

src/uipath/dev/server/frontend/src/store/useWebSocket.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,22 @@ export function useWebSocket() {
167167
agent.addToolApprovalRequest(tool_call_id, tool, args);
168168
break;
169169
}
170+
case "agent.thinking": {
171+
const { content } = msg.payload as { session_id: string; content: string };
172+
useAgentStore.getState().appendThinking(content);
173+
break;
174+
}
175+
case "agent.text_delta": {
176+
const { session_id: deltaSid, delta } = msg.payload as { session_id: string; delta: string };
177+
const agentDelta = useAgentStore.getState();
178+
if (!agentDelta.sessionId) agentDelta.setSessionId(deltaSid);
179+
agentDelta.appendAssistantText(delta, false);
180+
break;
181+
}
182+
case "agent.token_usage": {
183+
// Token usage received — could store for display if needed
184+
break;
185+
}
170186
case "agent.error": {
171187
const { message: errMsg } = msg.payload as { session_id: string; message: string };
172188
useAgentStore.getState().addError(errMsg);

src/uipath/dev/server/frontend/src/types/agent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export interface AgentToolCall {
1414

1515
export interface AgentMessage {
1616
id: string;
17-
role: "user" | "assistant" | "plan" | "tool";
17+
role: "user" | "assistant" | "plan" | "tool" | "thinking";
1818
content: string;
1919
timestamp: number;
2020
toolCall?: AgentToolCall;

src/uipath/dev/server/frontend/src/types/ws.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ export type ServerEventType =
1616
| "agent.tool_use"
1717
| "agent.tool_result"
1818
| "agent.tool_approval"
19-
| "agent.error";
19+
| "agent.error"
20+
| "agent.thinking"
21+
| "agent.text_delta"
22+
| "agent.token_usage";
2023

2124
export interface ServerMessage {
2225
type: ServerEventType;

src/uipath/dev/server/static/assets/ChatPanel-BcW6Jtpz.js renamed to src/uipath/dev/server/static/assets/ChatPanel-DAnMwTFj.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/uipath/dev/server/static/assets/index-B2xfJE6O.js renamed to src/uipath/dev/server/static/assets/index-DYl0Xnov.js

Lines changed: 21 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)