Skip to content

Commit 914eee1

Browse files
authored
Merge pull request #821 from benodiwal/feat/sidebar-agent-info
Feat/sidebar agent info
2 parents 863e533 + 5013147 commit 914eee1

5 files changed

Lines changed: 388 additions & 25 deletions

File tree

pkg/runtime/event.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,3 +349,94 @@ func MCPInitFinished(agentName string) Event {
349349
AgentContext: AgentContext{AgentName: agentName},
350350
}
351351
}
352+
353+
// AgentInfoEvent is sent when agent information is available or changes
354+
type AgentInfoEvent struct {
355+
Type string `json:"type"`
356+
AgentName string `json:"agent_name"`
357+
Model string `json:"model"`
358+
Description string `json:"description"`
359+
AgentContext
360+
}
361+
362+
func AgentInfo(agentName, model, description string) Event {
363+
return &AgentInfoEvent{
364+
Type: "agent_info",
365+
AgentName: agentName,
366+
Model: model,
367+
Description: description,
368+
AgentContext: AgentContext{AgentName: agentName},
369+
}
370+
}
371+
372+
// TeamInfoEvent is sent when team information is available
373+
type TeamInfoEvent struct {
374+
Type string `json:"type"`
375+
AvailableAgents []string `json:"available_agents"`
376+
CurrentAgent string `json:"current_agent"`
377+
AgentContext
378+
}
379+
380+
func TeamInfo(availableAgents []string, currentAgent string) Event {
381+
return &TeamInfoEvent{
382+
Type: "team_info",
383+
AvailableAgents: availableAgents,
384+
CurrentAgent: currentAgent,
385+
AgentContext: AgentContext{AgentName: currentAgent},
386+
}
387+
}
388+
389+
// AgentSwitchingEvent is sent when agent switching starts or stops
390+
type AgentSwitchingEvent struct {
391+
Type string `json:"type"`
392+
Switching bool `json:"switching"`
393+
FromAgent string `json:"from_agent,omitempty"`
394+
ToAgent string `json:"to_agent,omitempty"`
395+
AgentContext
396+
}
397+
398+
func AgentSwitching(switching bool, fromAgent, toAgent string) Event {
399+
currentAgent := fromAgent
400+
if toAgent != "" {
401+
currentAgent = toAgent
402+
}
403+
return &AgentSwitchingEvent{
404+
Type: "agent_switching",
405+
Switching: switching,
406+
FromAgent: fromAgent,
407+
ToAgent: toAgent,
408+
AgentContext: AgentContext{AgentName: currentAgent},
409+
}
410+
}
411+
412+
// ToolsetInfoEvent is sent when toolset information is available
413+
type ToolsetInfoEvent struct {
414+
Type string `json:"type"`
415+
AvailableTools int `json:"available_tools"`
416+
AgentContext
417+
}
418+
419+
func ToolsetInfo(availableTools int, agentName string) Event {
420+
return &ToolsetInfoEvent{
421+
Type: "toolset_info",
422+
AvailableTools: availableTools,
423+
AgentContext: AgentContext{AgentName: agentName},
424+
}
425+
}
426+
427+
// ToolStatusEvent is sent when a tool's execution status changes
428+
type ToolStatusEvent struct {
429+
Type string `json:"type"`
430+
ToolName string `json:"tool_name"`
431+
Status string `json:"status"` // running, completed, failed
432+
AgentContext
433+
}
434+
435+
func ToolStatus(toolName, status, agentName string) Event {
436+
return &ToolStatusEvent{
437+
Type: "tool_status",
438+
ToolName: toolName,
439+
Status: status,
440+
AgentContext: AgentContext{AgentName: agentName},
441+
}
442+
}

pkg/runtime/runtime.go

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,17 @@ func (r *LocalRuntime) RunStream(ctx context.Context, sess *session.Session) <-c
236236
// Set elicitation handler on all MCP toolsets before getting tools
237237
a := r.CurrentAgent()
238238

239+
// Emit agent information for sidebar display
240+
var modelID string
241+
if model := a.Model(); model != nil {
242+
modelID = model.ID()
243+
}
244+
events <- AgentInfo(a.Name(), modelID, a.Description())
245+
246+
// Emit team information
247+
availableAgents := r.team.AgentNames()
248+
events <- TeamInfo(availableAgents, r.currentAgent)
249+
239250
r.emitAgentWarnings(a, events)
240251

241252
for _, toolset := range a.ToolSets() {
@@ -251,6 +262,9 @@ func (r *LocalRuntime) RunStream(ctx context.Context, sess *session.Session) <-c
251262
return
252263
}
253264

265+
// Emit toolset information
266+
events <- ToolsetInfo(len(agentTools), r.currentAgent)
267+
254268
messages := sess.GetMessages(a)
255269
if sess.SendUserMessage {
256270
events <- UserMessage(messages[len(messages)-1].Content)
@@ -789,6 +803,9 @@ func (r *LocalRuntime) runTool(ctx context.Context, tool tools.Tool, toolCall to
789803

790804
events <- ToolCall(toolCall, tool, a.Name())
791805

806+
// Emit tool status: running
807+
events <- ToolStatus(toolCall.Function.Name, "running", a.Name())
808+
792809
var res *tools.ToolCallResult
793810
var err error
794811
var duration time.Duration
@@ -803,17 +820,23 @@ func (r *LocalRuntime) runTool(ctx context.Context, tool tools.Tool, toolCall to
803820
// Synthesize a cancellation response so the transcript remains consistent
804821
res = &tools.ToolCallResult{Output: "The tool call was canceled by the user."}
805822
span.SetStatus(codes.Ok, "tool handler canceled by user")
823+
// Emit tool status: failed (cancelled)
824+
events <- ToolStatus(toolCall.Function.Name, "failed", a.Name())
806825
} else {
807826
span.RecordError(err)
808827
span.SetStatus(codes.Error, "tool handler error")
809828
slog.Error("Error calling tool", "tool", toolCall.Function.Name, "error", err)
810829
res = &tools.ToolCallResult{
811830
Output: fmt.Sprintf("Error calling tool: %v", err),
812831
}
832+
// Emit tool status: failed
833+
events <- ToolStatus(toolCall.Function.Name, "failed", a.Name())
813834
}
814835
} else {
815836
span.SetStatus(codes.Ok, "tool handler completed")
816837
slog.Debug("Agent tool call completed", "tool", toolCall.Function.Name, "output_length", len(res.Output))
838+
// Emit tool status: completed
839+
events <- ToolStatus(toolCall.Function.Name, "completed", a.Name())
817840
}
818841

819842
events <- ToolCallResponse(toolCall, tool, res.Output, a.Name())
@@ -950,8 +973,35 @@ func (r *LocalRuntime) handleTaskTransfer(ctx context.Context, sess *session.Ses
950973
slog.Debug("Transferring task to agent", "from_agent", a.Name(), "to_agent", params.Agent, "task", params.Task)
951974

952975
ca := r.currentAgent
976+
977+
// Emit agent switching start event
978+
evts <- AgentSwitching(true, ca, params.Agent)
979+
953980
r.currentAgent = params.Agent
954-
defer func() { r.currentAgent = ca }()
981+
defer func() {
982+
r.currentAgent = ca
983+
984+
// Emit agent switching end event
985+
evts <- AgentSwitching(false, params.Agent, ca)
986+
987+
// Restore original agent info in sidebar
988+
if originalAgent, err := r.team.Agent(ca); err == nil {
989+
var modelID string
990+
if model := originalAgent.Model(); model != nil {
991+
modelID = model.ID()
992+
}
993+
evts <- AgentInfo(originalAgent.Name(), modelID, originalAgent.Description())
994+
}
995+
}()
996+
997+
// Emit agent info for the new agent
998+
if newAgent, err := r.team.Agent(params.Agent); err == nil {
999+
var modelID string
1000+
if model := newAgent.Model(); model != nil {
1001+
modelID = model.ID()
1002+
}
1003+
evts <- AgentInfo(newAgent.Name(), modelID, newAgent.Description())
1004+
}
9551005

9561006
memberAgentTask := "You are a member of a team of agents. Your goal is to complete the following task:"
9571007
memberAgentTask += fmt.Sprintf("\n\n<task>\n%s\n</task>", params.Task)

pkg/runtime/runtime_test.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,9 @@ func TestSimple(t *testing.T) {
211211
events := runSession(t, sess, stream)
212212

213213
expectedEvents := []Event{
214+
AgentInfo("root", "test/mock-model", ""),
215+
TeamInfo([]string{"root"}, "root"),
216+
ToolsetInfo(0, "root"),
214217
UserMessage("Hi"),
215218
StreamStarted(sess.ID, "root"),
216219
AgentChoice("root", "Hello"),
@@ -236,6 +239,9 @@ func TestMultipleContentChunks(t *testing.T) {
236239
events := runSession(t, sess, stream)
237240

238241
expectedEvents := []Event{
242+
AgentInfo("root", "test/mock-model", ""),
243+
TeamInfo([]string{"root"}, "root"),
244+
ToolsetInfo(0, "root"),
239245
UserMessage("Please greet me"),
240246
StreamStarted(sess.ID, "root"),
241247
AgentChoice("root", "Hello "),
@@ -263,6 +269,9 @@ func TestWithReasoning(t *testing.T) {
263269
events := runSession(t, sess, stream)
264270

265271
expectedEvents := []Event{
272+
AgentInfo("root", "test/mock-model", ""),
273+
TeamInfo([]string{"root"}, "root"),
274+
ToolsetInfo(0, "root"),
266275
UserMessage("Hi"),
267276
StreamStarted(sess.ID, "root"),
268277
AgentChoiceReasoning("root", "Let me think about this..."),
@@ -289,6 +298,9 @@ func TestMixedContentAndReasoning(t *testing.T) {
289298
events := runSession(t, sess, stream)
290299

291300
expectedEvents := []Event{
301+
AgentInfo("root", "test/mock-model", ""),
302+
TeamInfo([]string{"root"}, "root"),
303+
ToolsetInfo(0, "root"),
292304
UserMessage("Hi there"),
293305
StreamStarted(sess.ID, "root"),
294306
AgentChoiceReasoning("root", "The user wants a greeting"),
@@ -338,13 +350,16 @@ func TestErrorEvent(t *testing.T) {
338350
events = append(events, ev)
339351
}
340352

341-
require.Len(t, events, 4)
342-
require.IsType(t, &UserMessageEvent{}, events[0])
343-
require.IsType(t, &StreamStartedEvent{}, events[1])
344-
require.IsType(t, &ErrorEvent{}, events[2])
345-
require.IsType(t, &StreamStoppedEvent{}, events[3])
353+
require.Len(t, events, 7)
354+
require.IsType(t, &AgentInfoEvent{}, events[0])
355+
require.IsType(t, &TeamInfoEvent{}, events[1])
356+
require.IsType(t, &ToolsetInfoEvent{}, events[2])
357+
require.IsType(t, &UserMessageEvent{}, events[3])
358+
require.IsType(t, &StreamStartedEvent{}, events[4])
359+
require.IsType(t, &ErrorEvent{}, events[5])
360+
require.IsType(t, &StreamStoppedEvent{}, events[6])
346361

347-
errorEvent := events[2].(*ErrorEvent)
362+
errorEvent := events[5].(*ErrorEvent)
348363
require.Contains(t, errorEvent.Error, "simulated error")
349364
}
350365

@@ -374,9 +389,12 @@ func TestContextCancellation(t *testing.T) {
374389
events = append(events, ev)
375390
}
376391

377-
require.GreaterOrEqual(t, len(events), 2)
378-
require.IsType(t, &UserMessageEvent{}, events[0])
379-
require.IsType(t, &StreamStartedEvent{}, events[1])
392+
require.GreaterOrEqual(t, len(events), 5)
393+
require.IsType(t, &AgentInfoEvent{}, events[0])
394+
require.IsType(t, &TeamInfoEvent{}, events[1])
395+
require.IsType(t, &ToolsetInfoEvent{}, events[2])
396+
require.IsType(t, &UserMessageEvent{}, events[3])
397+
require.IsType(t, &StreamStartedEvent{}, events[4])
380398
require.IsType(t, &StreamStoppedEvent{}, events[len(events)-1])
381399
}
382400

0 commit comments

Comments
 (0)