diff --git a/chat/src/app/header.tsx b/chat/src/app/header.tsx
index 85c2836..615d2a3 100644
--- a/chat/src/app/header.tsx
+++ b/chat/src/app/header.tsx
@@ -1,10 +1,10 @@
"use client";
-import { useChat } from "@/components/chat-provider";
-import { ModeToggle } from "../components/mode-toggle";
+import {AgentTypeColorCoding, useChat} from "@/components/chat-provider";
+import {ModeToggle} from "@/components/mode-toggle";
export function Header() {
- const { serverStatus } = useChat();
+ const {serverStatus, agentType} = useChat();
return (
);
diff --git a/chat/src/components/chat-provider.tsx b/chat/src/components/chat-provider.tsx
index d57c3c8..b6051cc 100644
--- a/chat/src/components/chat-provider.tsx
+++ b/chat/src/components/chat-provider.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useSearchParams } from "next/navigation";
+import {useSearchParams} from "next/navigation";
import {
useState,
useEffect,
@@ -9,7 +9,7 @@ import {
PropsWithChildren,
useContext,
} from "react";
-import { toast } from "sonner";
+import {toast} from "sonner";
interface Message {
id: number;
@@ -32,6 +32,7 @@ interface MessageUpdateEvent {
interface StatusChangeEvent {
status: string;
+ agent_type: string;
}
function isDraftMessage(message: Message | DraftMessage): boolean {
@@ -42,11 +43,31 @@ type MessageType = "user" | "raw";
export type ServerStatus = "stable" | "running" | "offline" | "unknown";
+export type AgentType = "claude" | "goose" | "aider" | "gemini" | "amp" | "codex" | "cursor" | "cursor-agent" | "custom" | "unknown";
+
+export type AgentColorDisplayNamePair = {
+ displayName: string;
+ color: string;
+}
+
+export const AgentTypeColorCoding: Record, AgentColorDisplayNamePair> = {
+ claude: {color: "bg-blue-300 ring-blue-300/35", displayName: "Claude Code"},
+ goose: {color: "bg-green-300 ring-green-300/35", displayName: "Goose"},
+ aider: {color: "bg-yellow-300 ring-yellow-300/35", displayName: "Aider"},
+ gemini: {color: "bg-purple-300 ring-purple-300/35", displayName: "Gemini"},
+ amp: {color: "bg-pink-300 ring-pink-300/35", displayName: "Amp"},
+ codex: {color: "bg-orange-300 ring-orange-300/35", displayName: "Codex"},
+ cursor: {color: "bg-lime-300 ring-lime-300/35", displayName: "Cursor Agent"},
+ "cursor-agent": {color: "bg-lime-300 ring-lime-300/35", displayName: "Cursor Agent"},
+ custom: {color: "bg-gray-300 ring-gray-300/35", displayName: "Custom"}
+}
+
interface ChatContextValue {
messages: (Message | DraftMessage)[];
loading: boolean;
serverStatus: ServerStatus;
sendMessage: (message: string, type?: MessageType) => void;
+ agentType: AgentType;
}
const ChatContext = createContext(undefined);
@@ -86,10 +107,11 @@ const useAgentAPIUrl = (): string => {
return agentAPIURL;
};
-export function ChatProvider({ children }: PropsWithChildren) {
+export function ChatProvider({children}: PropsWithChildren) {
const [messages, setMessages] = useState<(Message | DraftMessage)[]>([]);
const [loading, setLoading] = useState(false);
const [serverStatus, setServerStatus] = useState("unknown");
+ const [agentType, setAgentType] = useState("custom");
const eventSourceRef = useRef(null);
const agentAPIUrl = useAgentAPIUrl();
@@ -162,6 +184,9 @@ export function ChatProvider({ children }: PropsWithChildren) {
} else {
setServerStatus("unknown");
}
+
+ // Set agent type
+ setAgentType(data.agent_type === "" ? "unknown" : data.agent_type as AgentType);
});
// Handle connection open (server is online)
@@ -211,7 +236,7 @@ export function ChatProvider({ children }: PropsWithChildren) {
if (type === "user") {
setMessages((prevMessages) => [
...prevMessages,
- { role: "user", content },
+ {role: "user", content},
]);
setLoading(true);
}
@@ -235,7 +260,7 @@ export function ChatProvider({ children }: PropsWithChildren) {
const messages =
"errors" in errorData
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
- errorData.errors.map((e: any) => e.message).join(", ")
+ errorData.errors.map((e: any) => e.message).join(", ")
: "";
const fullDetail = `${detail}: ${messages}`;
@@ -250,7 +275,7 @@ export function ChatProvider({ children }: PropsWithChildren) {
const messages =
"errors" in error
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
- error.errors.map((e: any) => e.message).join("\n")
+ error.errors.map((e: any) => e.message).join("\n")
: "";
const fullDetail = `${detail}: ${messages}`;
@@ -275,6 +300,7 @@ export function ChatProvider({ children }: PropsWithChildren) {
loading,
sendMessage,
serverStatus,
+ agentType,
}}
>
{children}
diff --git a/lib/httpapi/events.go b/lib/httpapi/events.go
index 1e6281d..73eff07 100644
--- a/lib/httpapi/events.go
+++ b/lib/httpapi/events.go
@@ -44,7 +44,8 @@ type MessageUpdateBody struct {
}
type StatusChangeBody struct {
- Status AgentStatus `json:"status" doc:"Agent status"`
+ Status AgentStatus `json:"status" doc:"Agent status"`
+ AgentType mf.AgentType `json:"agent_type" doc:"Type of the agent being used by the server."`
}
type ScreenUpdateBody struct {
@@ -60,6 +61,7 @@ type EventEmitter struct {
mu sync.Mutex
messages []st.ConversationMessage
status AgentStatus
+ agentType mf.AgentType
chans map[int]chan Event
chanIdx int
subscriptionBufSize int
@@ -147,7 +149,7 @@ func (e *EventEmitter) UpdateMessagesAndEmitChanges(newMessages []st.Conversatio
e.messages = newMessages
}
-func (e *EventEmitter) UpdateStatusAndEmitChanges(newStatus st.ConversationStatus) {
+func (e *EventEmitter) UpdateStatusAndEmitChanges(newStatus st.ConversationStatus, agentType mf.AgentType) {
e.mu.Lock()
defer e.mu.Unlock()
@@ -156,8 +158,9 @@ func (e *EventEmitter) UpdateStatusAndEmitChanges(newStatus st.ConversationStatu
return
}
- e.notifyChannels(EventTypeStatusChange, StatusChangeBody{Status: newAgentStatus})
+ e.notifyChannels(EventTypeStatusChange, StatusChangeBody{Status: newAgentStatus, AgentType: agentType})
e.status = newAgentStatus
+ e.agentType = agentType
}
func (e *EventEmitter) UpdateScreenAndEmitChanges(newScreen string) {
@@ -183,7 +186,7 @@ func (e *EventEmitter) currentStateAsEvents() []Event {
}
events = append(events, Event{
Type: EventTypeStatusChange,
- Payload: StatusChangeBody{Status: e.status},
+ Payload: StatusChangeBody{Status: e.status, AgentType: e.agentType},
})
events = append(events, Event{
Type: EventTypeScreenUpdate,
diff --git a/lib/httpapi/events_test.go b/lib/httpapi/events_test.go
index 23a1d36..46ccea5 100644
--- a/lib/httpapi/events_test.go
+++ b/lib/httpapi/events_test.go
@@ -5,6 +5,7 @@ import (
"testing"
"time"
+ mf "github.com/coder/agentapi/lib/msgfmt"
st "github.com/coder/agentapi/lib/screentracker"
"github.com/stretchr/testify/assert"
)
@@ -51,11 +52,11 @@ func TestEventEmitter(t *testing.T) {
Payload: MessageUpdateBody{Id: 2, Message: "What's up?", Role: st.ConversationRoleAgent, Time: now},
}, newEvent)
- emitter.UpdateStatusAndEmitChanges(st.ConversationStatusStable)
+ emitter.UpdateStatusAndEmitChanges(st.ConversationStatusStable, mf.AgentTypeAider)
newEvent = <-ch
assert.Equal(t, Event{
Type: EventTypeStatusChange,
- Payload: StatusChangeBody{Status: AgentStatusStable},
+ Payload: StatusChangeBody{Status: AgentStatusStable, AgentType: mf.AgentTypeAider},
}, newEvent)
})
diff --git a/lib/httpapi/models.go b/lib/httpapi/models.go
index 8f969f9..4787426 100644
--- a/lib/httpapi/models.go
+++ b/lib/httpapi/models.go
@@ -3,6 +3,7 @@ package httpapi
import (
"time"
+ mf "github.com/coder/agentapi/lib/msgfmt"
st "github.com/coder/agentapi/lib/screentracker"
"github.com/coder/agentapi/lib/util"
"github.com/danielgtaylor/huma/v2"
@@ -35,7 +36,8 @@ type Message struct {
// StatusResponse represents the server status
type StatusResponse struct {
Body struct {
- Status AgentStatus `json:"status" doc:"Current agent status. 'running' means that the agent is processing a message, 'stable' means that the agent is idle and waiting for input."`
+ Status AgentStatus `json:"status" doc:"Current agent status. 'running' means that the agent is processing a message, 'stable' means that the agent is idle and waiting for input."`
+ AgentType mf.AgentType `json:"agent_type" doc:"Type of the agent being used by the server."`
}
}
diff --git a/lib/httpapi/server.go b/lib/httpapi/server.go
index 986d071..a469b62 100644
--- a/lib/httpapi/server.go
+++ b/lib/httpapi/server.go
@@ -265,7 +265,7 @@ func (s *Server) StartSnapshotLoop(ctx context.Context) {
s.conversation.StartSnapshotLoop(ctx)
go func() {
for {
- s.emitter.UpdateStatusAndEmitChanges(s.conversation.Status())
+ s.emitter.UpdateStatusAndEmitChanges(s.conversation.Status(), s.agentType)
s.emitter.UpdateMessagesAndEmitChanges(s.conversation.Messages())
s.emitter.UpdateScreenAndEmitChanges(s.conversation.Screen())
time.Sleep(snapshotInterval)
@@ -329,6 +329,7 @@ func (s *Server) getStatus(ctx context.Context, input *struct{}) (*StatusRespons
resp := &StatusResponse{}
resp.Body.Status = agentStatus
+ resp.Body.AgentType = s.agentType
return resp, nil
}
diff --git a/openapi.json b/openapi.json
index b79f9c9..6aa2776 100644
--- a/openapi.json
+++ b/openapi.json
@@ -270,13 +270,18 @@
"StatusChangeBody": {
"additionalProperties": false,
"properties": {
+ "agent_type": {
+ "description": "Type of the agent being used by the server.",
+ "type": "string"
+ },
"status": {
"$ref": "#/components/schemas/AgentStatus",
"description": "Agent status"
}
},
"required": [
- "status"
+ "status",
+ "agent_type"
],
"type": "object"
},
@@ -292,13 +297,18 @@
"readOnly": true,
"type": "string"
},
+ "agent_type": {
+ "description": "Type of the agent being used by the server.",
+ "type": "string"
+ },
"status": {
"$ref": "#/components/schemas/AgentStatus",
"description": "Current agent status. 'running' means that the agent is processing a message, 'stable' means that the agent is idle and waiting for input."
}
},
"required": [
- "status"
+ "status",
+ "agent_type"
],
"type": "object"
}
@@ -326,10 +336,10 @@
{
"properties": {
"data": {
- "$ref": "#/components/schemas/MessageUpdateBody"
+ "$ref": "#/components/schemas/StatusChangeBody"
},
"event": {
- "const": "message_update",
+ "const": "status_change",
"description": "The event name.",
"type": "string"
},
@@ -346,16 +356,16 @@
"data",
"event"
],
- "title": "Event message_update",
+ "title": "Event status_change",
"type": "object"
},
{
"properties": {
"data": {
- "$ref": "#/components/schemas/StatusChangeBody"
+ "$ref": "#/components/schemas/MessageUpdateBody"
},
"event": {
- "const": "status_change",
+ "const": "message_update",
"description": "The event name.",
"type": "string"
},
@@ -372,7 +382,7 @@
"data",
"event"
],
- "title": "Event status_change",
+ "title": "Event message_update",
"type": "object"
}
]
@@ -497,4 +507,4 @@
}
}
}
-}
\ No newline at end of file
+}