Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: gui - WIP #3750

Merged
merged 24 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ async function startAgent(
}
}

async function stopAgent(
runtime: IAgentRuntime,
server: AgentServer
) {
server.unregisterAgent(runtime);
}

const checkPortAvailable = (port: number): Promise<boolean> => {
return new Promise((resolve) => {
const server = net.createServer();
Expand All @@ -163,6 +170,9 @@ const startAgents = async () => {
logger.info(`Starting agent for character ${character.name}`);
return startAgent(character, server);
};
server.stopAgent = (runtime: IAgentRuntime) => {
stopAgent(runtime, server);
}
server.loadCharacterTryPath = loadCharacterTryPath;
server.jsonToCharacter = jsonToCharacter;

Expand Down
113 changes: 46 additions & 67 deletions packages/agent/src/server/api/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -558,85 +558,41 @@ export function agentRouter(
}
});

router.post('/start', async (req, res) => {
logger.info("[AGENT START] Received request to start a new agent");
const { characterPath, characterJson, agentId } = req.body;

// Log request details
if (agentId) {
logger.debug(`[AGENT START] Using agent ID: ${agentId}`);
} else if (characterPath) {
logger.debug(`[AGENT START] Using character path: ${characterPath}`);
} else if (characterJson) {
logger.debug("[AGENT START] Using provided character JSON");
} else {
logger.warn("[AGENT START] No agent ID, character path, or JSON provided");
router.post('/:agentId/start', async (req, res) => {
const agentId = req.params.agentId;

const validAgentId = validateUuid(agentId);

if (!validAgentId) {
const errorMessage = "Invalid agent ID format";
logger.error(`[AGENT START] ${errorMessage}`);
throw new Error(errorMessage);
}


if (!server?.database) {
const errorMessage = "Failed to find server database";
logger.error(`[AGENT START] ${errorMessage}`);
throw new Error(errorMessage);
}

try {
let character: Character;
let source = "";

// Try to find agent by ID first if provided
if (agentId) {
logger.debug(`[AGENT START] Looking for agent in database: ${agentId}`);
const validAgentId = validateUuid(agentId);

if (!validAgentId) {
const errorMessage = "Invalid agent ID format";
logger.error(`[AGENT START] ${errorMessage}`);
throw new Error(errorMessage);
}

if (server?.database) {
const agent = await server.database.getAgent(validAgentId);
if (agent) {
character = agent.character;
source = "database";
logger.debug(`[AGENT START] Found agent in database: ${agent.character.name} (${validAgentId})`);
} else {
logger.warn(`[AGENT START] Agent not found in database by ID: ${validAgentId}`);
}
}
}

// If agent ID wasn't provided or agent wasn't found, fallback to other methods
if (!character) {
if (characterJson) {
logger.debug("[AGENT START] Parsing character from JSON");
character = await server?.jsonToCharacter(characterJson);
source = "json";
} else if (characterPath) {
logger.debug(`[AGENT START] Loading character from path: ${characterPath}`);
character = await server?.loadCharacterTryPath(characterPath);
source = "path";
} else if (!agentId) { // Only throw if agentId wasn't provided
const errorMessage = "No character path or JSON provided";
logger.error(`[AGENT START] ${errorMessage}`);
throw new Error(errorMessage);
} else {
const errorMessage = `Agent with ID ${agentId} not found`;
logger.error(`[AGENT START] ${errorMessage}`);
throw new Error(errorMessage);
}
}

// Check if character was found
if (!character) {
const agentData = await server.database.getAgent(validAgentId);

if (!agentData) {
const errorMessage = "No valid agent or character information provided";
logger.error(`[AGENT START] ${errorMessage}`);
throw new Error(errorMessage);
}

logger.info(`[AGENT START] Starting agent for character: ${character.name} (source: ${source})`);
const agent = await server?.startAgent(character);
logger.success(`[AGENT START] Agent started successfully: ${character.name} (${character.id})`);
logger.info(`[AGENT START] Starting agent for character: ${agentData.name}`);
const agent = await server?.startAgent(agentData);
logger.success(`[AGENT START] Agent started successfully: ${agentData.name} (${agentData.id})`);

res.json({
id: agent.agentId,
character: agent.character,
});
logger.debug(`[AGENT START] Successfully returned agent data for: ${character.name}`);
logger.debug(`[AGENT START] Successfully returned agent data for: ${agentData.name}`);
} catch (e) {
logger.error(`[AGENT START] Error starting agent: ${e}`);
res.status(400).json({
Expand All @@ -646,6 +602,29 @@ export function agentRouter(
}
});

router.post('/:agentId/stop', (req, res) => {
const agentId = req.params.agentId;
const validAgentId = validateUuid(agentId);

if (!validAgentId) {
const errorMessage = "Invalid agent ID format";
logger.error(`[AGENT STOP] ${errorMessage}`);
throw new Error(errorMessage);
}

const agentRuntime = server.agents.get(validAgentId);

if (!agentRuntime) {
const errorMessage = "Failed to find agent runtime";
logger.error(`[AGENT STOP] ${errorMessage}`);
throw new Error(errorMessage);
}

const response = server.stopAgent(agentRuntime);
logger.success(`[AGENT STOP] Agent stoped successfully)`);
res.json(response);
});

router.post('/:agentId/status', async (req, res) => {
const agentId = validateUuid(req.params.agentId);
if (!agentId) {
Expand Down
7 changes: 7 additions & 0 deletions packages/agent/src/server/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ export function createApiRouter(
});
});

router.get('/active-agents', (_req, res) => {
const activeAgentIds = [...server.agents.entries()]
.map(([id]) => id);

res.json(activeAgentIds);
});

return router;
}

3 changes: 2 additions & 1 deletion packages/agent/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@ export interface ServerOptions {

export class AgentServer {
public app: express.Application;
private agents: Map<string, IAgentRuntime>;
public agents: Map<string, IAgentRuntime>;
public server: any;


public database: any;
public startAgent!: (character: Character) => Promise<IAgentRuntime>;
public stopAgent!: (runtime: IAgentRuntime) => void;
public loadCharacterTryPath!: (characterPath: string) => Promise<Character>;
public jsonToCharacter!: (character: unknown) => Promise<Character>;

Expand Down
11 changes: 5 additions & 6 deletions packages/client/src/components/agent-creator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useToast } from "@/hooks/use-toast";
import { useQueryClient } from "@tanstack/react-query";
import type { Character } from "@elizaos/core";
import type { Agent } from "@elizaos/core";
import CharacterForm from "@/components/character-form";
import PluginsPanel from "./plugins-panel";
import SecretPanel from "./secret-panel";
Expand All @@ -15,15 +15,15 @@ const defaultCharacter = {
bio: [] as string[],
topics: [] as string[],
adjectives: [] as string[],
}
} as Agent

export default function AgentCreator() {
const navigate = useNavigate();
const { toast } = useToast();
const queryClient = useQueryClient();
const [characterValue, setCharacterValue] = useState<Character>({...defaultCharacter});
const [characterValue, setCharacterValue] = useState<Agent>({...defaultCharacter});

const ensureRequiredFields = (character: Character): Character => {
const ensureRequiredFields = (character: Agent): Agent => {
return {
...character,
bio: character.bio ?? [],
Expand All @@ -40,7 +40,7 @@ export default function AgentCreator() {
};
};

const handleSubmit = async (character: Character) => {
const handleSubmit = async (character: Agent) => {
try {
const completeCharacter = ensureRequiredFields(character);
await apiClient.createAgent(
Expand Down Expand Up @@ -75,7 +75,6 @@ export default function AgentCreator() {
description="Configure your AI character's behavior and capabilities"
onSubmit={handleSubmit}
onReset={() => setCharacterValue(defaultCharacter)}
submitButtonText="Save Changes"
isAgent={true}
customComponents={[
{
Expand Down
88 changes: 10 additions & 78 deletions packages/client/src/components/agent-settings.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,41 @@
import { useToast } from "@/hooks/use-toast";
import { apiClient } from "@/lib/api";
import type { Character, UUID } from "@elizaos/core";
import type { Agent, UUID } from "@elizaos/core";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useQueryClient } from "@tanstack/react-query";
import CharacterForm from "@/components/character-form";
import { useAgent } from "@/hooks/use-query-hooks";
import PluginsPanel from "./plugins-panel";
import SecretPanel from "./secret-panel";

// Define interface for agent data response that includes enabled property
interface AgentResponse {
id: UUID;
character: Character;
enabled: boolean;
}

export default function AgentSettings({ character, agentId }: { character: Character, agentId: UUID }) {
export default function AgentSettings({ agent, agentId }: { agent: Agent, agentId: UUID }) {
const { toast } = useToast();
const navigate = useNavigate();
const queryClient = useQueryClient();
const { data: agentData } = useAgent(agentId);

// The API actually returns an enabled property even though the type doesn't include it
const isAgentEnabled = Boolean((agentData as unknown as AgentResponse)?.enabled);
const [characterValue, setCharacterValue] = useState<Character>(character);
const [characterValue, setCharacterValue] = useState<Agent>(agent);

const handleSubmit = async (updatedChar: Character) => {
const handleSubmit = async (updatedAgent: Agent) => {
try {
// Call the API to update the agent's character
if (!agentId) {
throw new Error("Agent ID is missing");
}

// Make sure plugins are preserved
const mergedChar = {
...updatedChar,
const mergedAgent = {
...updatedAgent,
plugins: characterValue.plugins // Preserve the plugins from our local state
};

// Send the character update request to the agent endpoint
await apiClient.updateAgent(agentId, mergedChar);
await apiClient.updateAgent(agentId, mergedAgent);

// Invalidate both the agent query and the agents list
queryClient.invalidateQueries({ queryKey: ["agent", agentId] });
queryClient.invalidateQueries({ queryKey: ["agents"] });

navigate("/");

toast({
title: "Success",
Expand All @@ -60,73 +51,14 @@ export default function AgentSettings({ character, agentId }: { character: Chara
}
};

const handleStopAgent = async () => {
try {
if (!agentId) {
throw new Error("Agent ID is missing");
}

await apiClient.stopAgent(agentId);

// Invalidate queries before showing toast and navigating
queryClient.invalidateQueries({ queryKey: ["agent", agentId] });
queryClient.invalidateQueries({ queryKey: ["agents"] });

toast({
title: "Success",
description: "Agent stopped successfully",
});

// Navigate to home page after successfully stopping the agent
navigate('/');
} catch (error) {
toast({
title: "Error",
description: error instanceof Error ? error.message : "Failed to stop agent",
variant: "destructive",
});
throw error;
}
};

const handleStartAgent = async () => {
try {
if (!agentId) {
throw new Error("Agent ID is missing");
}

await apiClient.startAgentByName(character.name);

// Invalidate queries for fresh data
queryClient.invalidateQueries({ queryKey: ["agent", agentId] });
queryClient.invalidateQueries({ queryKey: ["agents"] });

toast({
title: "Success",
description: "Agent started successfully",
});
} catch (error) {
toast({
title: "Error",
description: error instanceof Error ? error.message : "Failed to start agent",
variant: "destructive",
});
throw error;
}
};

return (
<CharacterForm
characterValue={characterValue}
setCharacterValue={setCharacterValue}
title="Character Settings"
description="Configure your AI character's behavior and capabilities"
onSubmit={handleSubmit}
onReset={() => setCharacterValue(character)}
submitButtonText="Save Changes"
deleteButtonText={isAgentEnabled ? "Stop Agent" : "Start Agent"}
deleteButtonVariant={isAgentEnabled ? "destructive" : "default"}
onDelete={isAgentEnabled ? handleStopAgent : handleStartAgent}
onReset={() => setCharacterValue(agent)}
isAgent={true}
customComponents={[
{
Expand Down
Loading
Loading