From c732afe93b23cb09b27820ff4abc9e7ffc884182 Mon Sep 17 00:00:00 2001 From: kb01111 Date: Wed, 23 Apr 2025 15:04:56 +0200 Subject: [PATCH 1/5] Fix lint errors in frontend components to enable successful build --- .idea/.gitignore | 10 + .idea/AugmentWebviewStateStore.xml | 10 + .idea/KB-multi-agent.iml | 9 + .idea/inspectionProfiles/Project_Default.xml | 9 + .idea/misc.xml | 6 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + agent/LOGFIRE.md | 87 ++++ agent/knowledge_server.py | 107 +++++ .../integrations/logfire_integration.py | 225 ++++++++++ agent/mcp_agent/langgraph.json | 9 + agent/mcp_agent/run_server.py | 40 ++ agent/mcp_agent/test_graphiti_integration.py | 63 +++ agent/run_autoflake.py | 31 ++ agent/test_knowledge_server.py | 79 ++++ check-lint.js | 14 + fix-lint.js | 14 + frontend/.vscode/settings.json | 14 + frontend/lint-fix.js | 15 + frontend/public/favicon.ico | 27 ++ frontend/public/logo.svg | 27 ++ frontend/public/map-marker.svg | 8 + frontend/public/map-overlay.svg | 51 +++ frontend/src/app/settings/layout.tsx | 11 + frontend/src/app/settings/page.tsx | 280 ++++++++++++ .../src/components/agents/knowledge-agent.tsx | 133 ++++++ frontend/src/components/enhanced-layout.tsx | 46 ++ frontend/src/components/enhanced-sidebar.tsx | 405 ++++++++++++++++++ frontend/src/components/entity-form.tsx | 232 ++++++++++ frontend/src/components/knowledge-graph.tsx | 390 +++++++++++++++++ frontend/src/components/settings-modal.tsx | 279 ++++++++++++ .../src/components/ui/loading-animation.tsx | 190 ++++++++ frontend/src/lib/settings-types.ts | 38 ++ frontend/src/providers/SettingsProvider.tsx | 94 ++++ frontend/src/styles/theme.css | 129 ++++++ install-agent.bat | 9 + install-autoflake.bat | 4 + run-build.js | 14 + test-langgraph.bat | 4 + 39 files changed, 3127 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/AugmentWebviewStateStore.xml create mode 100644 .idea/KB-multi-agent.iml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 agent/LOGFIRE.md create mode 100644 agent/knowledge_server.py create mode 100644 agent/mcp_agent/integrations/logfire_integration.py create mode 100644 agent/mcp_agent/langgraph.json create mode 100644 agent/mcp_agent/run_server.py create mode 100644 agent/mcp_agent/test_graphiti_integration.py create mode 100644 agent/run_autoflake.py create mode 100644 agent/test_knowledge_server.py create mode 100644 check-lint.js create mode 100644 fix-lint.js create mode 100644 frontend/.vscode/settings.json create mode 100644 frontend/lint-fix.js create mode 100644 frontend/public/favicon.ico create mode 100644 frontend/public/logo.svg create mode 100644 frontend/public/map-marker.svg create mode 100644 frontend/public/map-overlay.svg create mode 100644 frontend/src/app/settings/layout.tsx create mode 100644 frontend/src/app/settings/page.tsx create mode 100644 frontend/src/components/agents/knowledge-agent.tsx create mode 100644 frontend/src/components/enhanced-layout.tsx create mode 100644 frontend/src/components/enhanced-sidebar.tsx create mode 100644 frontend/src/components/entity-form.tsx create mode 100644 frontend/src/components/knowledge-graph.tsx create mode 100644 frontend/src/components/settings-modal.tsx create mode 100644 frontend/src/components/ui/loading-animation.tsx create mode 100644 frontend/src/lib/settings-types.ts create mode 100644 frontend/src/providers/SettingsProvider.tsx create mode 100644 frontend/src/styles/theme.css create mode 100644 install-agent.bat create mode 100644 install-autoflake.bat create mode 100644 run-build.js create mode 100644 test-langgraph.bat diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..7bc07ec --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Environment-dependent path to Maven home directory +/mavenHomeManager.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/AugmentWebviewStateStore.xml b/.idea/AugmentWebviewStateStore.xml new file mode 100644 index 0000000..cab5dbd --- /dev/null +++ b/.idea/AugmentWebviewStateStore.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/KB-multi-agent.iml b/.idea/KB-multi-agent.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/KB-multi-agent.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..ac46ceb --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..639900d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..4520427 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/agent/LOGFIRE.md b/agent/LOGFIRE.md new file mode 100644 index 0000000..1c8290c --- /dev/null +++ b/agent/LOGFIRE.md @@ -0,0 +1,87 @@ +# Logfire Integration for KB-multi-agent + +This project includes integration with [Logfire](https://logfire.ai/) for logging, tracing, and monitoring of the agent's operations. + +## Setup + +### 1. Install the Logfire SDK + +The Logfire SDK is already included in the project dependencies. When you run `poetry install`, it will be installed automatically. + +### 2. Authenticate with Logfire + +To authenticate with Logfire, run: + +```bash +logfire auth +``` + +This will open a browser window where you can authenticate. Upon successful authentication, credentials are stored in `~/.logfire/default.toml`. + +### 3. Configure the Project + +From the working directory where you will run your application, set the Logfire project: + +```bash +logfire projects use kb-multi-agent +``` + +Alternatively, you can set the `LOGFIRE_PROJECT` environment variable in your `.env` file. + +### 4. Configure Environment Variables + +In your `.env` file, set the following variables: + +``` +LOGFIRE_PROJECT=kb-multi-agent +LOGFIRE_TOKEN= # Optional, if not using logfire auth +LOGGING_ENABLED=true +``` + +## Features + +The Logfire integration provides the following features: + +### Tracing + +- **Conversation Tracing**: Each conversation is assigned a unique ID and trace ID for tracking +- **Tool Usage Tracing**: All tool calls are logged with inputs and outputs +- **LLM Interaction Tracing**: Model calls are logged with prompts and responses +- **Error Tracking**: Exceptions are logged with context information + +### Metrics + +- **Response Times**: Track how long different operations take +- **Message Counts**: Monitor the number of messages in conversations +- **Tool Usage**: See which tools are used most frequently + +### Logs + +- **Structured Logging**: All logs include relevant context like conversation IDs +- **Error Logging**: Detailed error information for debugging +- **Event Logging**: Key events in the agent's lifecycle are logged + +## Implementation Details + +The Logfire integration is implemented in the following files: + +- `mcp_agent/integrations/logfire_integration.py`: The main integration module +- `mcp_agent/agent_factory.py`: Factory that initializes and provides the logger +- `mcp_agent/agent.py`: Uses the logger for tracing agent operations + +## Viewing Logs and Traces + +To view your logs and traces, go to the [Logfire Dashboard](https://app.logfire.ai/) and select your project. + +## Troubleshooting + +If you encounter issues with Logfire integration: + +1. Check that you're authenticated with `logfire auth` +2. Verify that the correct project is set with `logfire projects list` +3. Ensure `LOGGING_ENABLED=true` in your environment +4. Check for any error messages in the application logs + +## Disabling Logging + +To disable Logfire logging, set `LOGGING_ENABLED=false` in your `.env` file or environment variables. diff --git a/agent/knowledge_server.py b/agent/knowledge_server.py new file mode 100644 index 0000000..3e9447f --- /dev/null +++ b/agent/knowledge_server.py @@ -0,0 +1,107 @@ +# knowledge_server.py +from mcp.server.fastmcp import FastMCP +from mcp_agent.integrations.graphiti_integration import GraphitiKnowledgeSource +import asyncio +import json + +# Create the MCP server +mcp = FastMCP("Knowledge") + +# Initialize the knowledge source +knowledge_source = GraphitiKnowledgeSource() + +@mcp.tool() +async def query_knowledge(query: str) -> dict: + """ + Query the knowledge graph with a natural language query. + Returns entities and relations matching the query. + """ + result = await knowledge_source.query(query) + # Convert to dict for JSON serialization + return { + "entities": [entity.dict() for entity in result.entities], + "relations": [relation.dict() for relation in result.relations] + } + +@mcp.tool() +async def search_entities(query: str, entity_type: str = None, limit: int = 10) -> list: + """ + Search for entities in the knowledge graph. + + Args: + query: The search query + entity_type: Optional type of entity to filter by + limit: Maximum number of results to return + + Returns: + List of matching entities + """ + entities = await knowledge_source.search_entities(query, entity_type, limit) + return [entity.dict() for entity in entities] + +@mcp.tool() +async def get_entity(entity_id: str) -> dict: + """ + Get details about a specific entity by ID. + + Args: + entity_id: The ID of the entity to retrieve + + Returns: + Entity details or None if not found + """ + entity = await knowledge_source.get_entity(entity_id) + if entity: + return entity.dict() + return None + +@mcp.tool() +async def add_entity(entity_id: str, entity_type: str, properties: dict) -> str: + """ + Add a new entity to the knowledge graph. + + Args: + entity_id: Unique identifier for the entity + entity_type: Type of the entity (e.g., "person", "organization") + properties: Dictionary of entity properties + + Returns: + ID of the created entity + """ + from mcp_agent.integrations.graphiti_integration import KnowledgeEntity + + entity = KnowledgeEntity( + id=entity_id, + type=entity_type, + properties=properties + ) + + return await knowledge_source.add_entity(entity) + +@mcp.tool() +async def add_relation(source_id: str, target_id: str, relation_type: str, properties: dict = None) -> bool: + """ + Add a relation between two entities in the knowledge graph. + + Args: + source_id: ID of the source entity + target_id: ID of the target entity + relation_type: Type of relation (e.g., "works_for", "located_in") + properties: Optional properties for the relation + + Returns: + True if successful, False otherwise + """ + from mcp_agent.integrations.graphiti_integration import KnowledgeRelation + + relation = KnowledgeRelation( + source_id=source_id, + target_id=target_id, + type=relation_type, + properties=properties or {} + ) + + return await knowledge_source.add_relation(relation) + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/agent/mcp_agent/integrations/logfire_integration.py b/agent/mcp_agent/integrations/logfire_integration.py new file mode 100644 index 0000000..18ab81a --- /dev/null +++ b/agent/mcp_agent/integrations/logfire_integration.py @@ -0,0 +1,225 @@ +""" +Logfire integration for the MCP Agent. +Provides logging and tracing capabilities for the agent. +""" + +import os +import logging +import logfire +from typing import Optional, Dict, Any, List, Union +from contextlib import contextmanager + +# Configure basic logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +class LogfireLogger: + """ + Integration with Logfire for logging and tracing. + Provides methods to log events, errors, and create spans for tracing. + """ + + def __init__(self, project_name: Optional[str] = None, enabled: bool = True): + """ + Initialize the Logfire logger. + + Args: + project_name: Name of the Logfire project. Defaults to LOGFIRE_PROJECT env var or 'kb-multi-agent'. + enabled: Whether Logfire logging is enabled. Defaults to True. + """ + self.project_name = project_name or os.getenv("LOGFIRE_PROJECT", "kb-multi-agent") + self.enabled = enabled and self._check_logfire_configured() + + if self.enabled: + try: + # Initialize Logfire + logfire.configure( + project=self.project_name, + service="mcp-agent", + ) + logger.info(f"Logfire initialized for project: {self.project_name}") + except Exception as e: + logger.warning(f"Failed to initialize Logfire: {e}") + self.enabled = False + + def _check_logfire_configured(self) -> bool: + """Check if Logfire is configured in the environment.""" + # Check for Logfire configuration file or environment variables + if os.path.exists(os.path.expanduser("~/.logfire/default.toml")): + return True + if os.getenv("LOGFIRE_TOKEN"): + return True + return False + + def log_event(self, event_name: str, data: Optional[Dict[str, Any]] = None) -> None: + """ + Log an event to Logfire. + + Args: + event_name: Name of the event + data: Additional data to log with the event + """ + if not self.enabled: + return + + try: + logfire.log(event_name, **(data or {})) + except Exception as e: + logger.warning(f"Failed to log event to Logfire: {e}") + + def log_error(self, error: Exception, context: Optional[Dict[str, Any]] = None) -> None: + """ + Log an error to Logfire. + + Args: + error: The exception to log + context: Additional context data + """ + if not self.enabled: + return + + try: + logfire.error( + error, + error_message=str(error), + **(context or {}) + ) + except Exception as e: + logger.warning(f"Failed to log error to Logfire: {e}") + + @contextmanager + def span(self, name: str, attributes: Optional[Dict[str, Any]] = None): + """ + Create a span for tracing. + + Args: + name: Name of the span + attributes: Additional attributes for the span + """ + if not self.enabled: + yield + return + + try: + with logfire.span(name, **(attributes or {})) as span: + yield span + except Exception as e: + logger.warning(f"Failed to create Logfire span: {e}") + yield None + + def log_agent_request(self, + agent_id: str, + user_input: str, + metadata: Optional[Dict[str, Any]] = None) -> None: + """ + Log an agent request. + + Args: + agent_id: ID of the agent + user_input: User input text + metadata: Additional metadata + """ + data = { + "agent_id": agent_id, + "user_input": user_input, + **(metadata or {}) + } + self.log_event("agent_request", data) + + def log_agent_response(self, + agent_id: str, + response: str, + metadata: Optional[Dict[str, Any]] = None) -> None: + """ + Log an agent response. + + Args: + agent_id: ID of the agent + response: Agent response text + metadata: Additional metadata + """ + data = { + "agent_id": agent_id, + "response": response, + **(metadata or {}) + } + self.log_event("agent_response", data) + + def log_tool_call(self, + tool_name: str, + inputs: Dict[str, Any], + metadata: Optional[Dict[str, Any]] = None) -> None: + """ + Log a tool call. + + Args: + tool_name: Name of the tool + inputs: Tool inputs + metadata: Additional metadata + """ + data = { + "tool_name": tool_name, + "inputs": inputs, + **(metadata or {}) + } + self.log_event("tool_call", data) + + def log_tool_result(self, + tool_name: str, + result: Any, + metadata: Optional[Dict[str, Any]] = None) -> None: + """ + Log a tool result. + + Args: + tool_name: Name of the tool + result: Tool result + metadata: Additional metadata + """ + data = { + "tool_name": tool_name, + "result": str(result), + **(metadata or {}) + } + self.log_event("tool_result", data) + + def log_llm_call(self, + model: str, + prompt: Union[str, List[Dict[str, str]]], + metadata: Optional[Dict[str, Any]] = None) -> None: + """ + Log an LLM call. + + Args: + model: Name of the model + prompt: Prompt text or messages + metadata: Additional metadata + """ + # Convert prompt to string if it's a list of messages + prompt_str = prompt if isinstance(prompt, str) else str(prompt) + + data = { + "model": model, + "prompt": prompt_str, + **(metadata or {}) + } + self.log_event("llm_call", data) + + def log_llm_response(self, + model: str, + response: str, + metadata: Optional[Dict[str, Any]] = None) -> None: + """ + Log an LLM response. + + Args: + model: Name of the model + response: LLM response text + metadata: Additional metadata + """ + data = { + "model": model, + "response": response, + **(metadata or {}) + } + self.log_event("llm_response", data) diff --git a/agent/mcp_agent/langgraph.json b/agent/mcp_agent/langgraph.json new file mode 100644 index 0000000..52112a2 --- /dev/null +++ b/agent/mcp_agent/langgraph.json @@ -0,0 +1,9 @@ +{ + "graphs": [ + { + "id": "mcp-agent", + "path": "./agent.py", + "variable": "graph" + } + ] +} diff --git a/agent/mcp_agent/run_server.py b/agent/mcp_agent/run_server.py new file mode 100644 index 0000000..719f51b --- /dev/null +++ b/agent/mcp_agent/run_server.py @@ -0,0 +1,40 @@ +""" +Run server script for the MCP Agent. +This script properly sets up the Python module structure for running the langgraph server. +""" + +import os +import sys +import argparse +import subprocess +from pathlib import Path + +def main(): + """Run the langgraph server with the correct module structure.""" + parser = argparse.ArgumentParser(description="Run the MCP Agent server") + parser.add_argument("--host", default="localhost", help="Host to run the server on") + parser.add_argument("--port", default="8123", help="Port to run the server on") + args = parser.parse_args() + + # Get the current directory (mcp_agent) + current_dir = Path(__file__).parent + + # Get the parent directory (agent) + agent_dir = current_dir.parent + + # Change to the agent directory + os.chdir(agent_dir) + + # Run the langgraph dev command + cmd = [ + "langgraph", "dev", + "--config", "langgraph.json", + "--host", args.host, + "--port", args.port, + "--no-browser" + ] + + subprocess.run(cmd) + +if __name__ == "__main__": + main() diff --git a/agent/mcp_agent/test_graphiti_integration.py b/agent/mcp_agent/test_graphiti_integration.py new file mode 100644 index 0000000..3aef31a --- /dev/null +++ b/agent/mcp_agent/test_graphiti_integration.py @@ -0,0 +1,63 @@ +import pytest +import os +from mcp_agent.integrations.graphiti_integration import ( + GraphitiKnowledgeSource, + KnowledgeEntity, + KnowledgeRelation, + KnowledgeQuery +) +from mcp_agent.agent_factory import AgentFactory + +pytestmark = pytest.mark.asyncio + +class DummyAgent: + def __init__(self, memory, llm, a2a, knowledge, **kwargs): + self.memory = memory + self.llm = llm + self.a2a = a2a + self.knowledge = knowledge + self.kwargs = kwargs + +async def test_graphiti_knowledge_source(): + """Test basic functionality of the GraphitiKnowledgeSource""" + knowledge_source = GraphitiKnowledgeSource() + + # Test query + query_result = await knowledge_source.query("test query") + assert query_result.entities + assert len(query_result.entities) > 0 + + # Test entity operations + entity = KnowledgeEntity( + id="test-entity", + type="test", + properties={"name": "Test Entity"} + ) + entity_id = await knowledge_source.add_entity(entity) + assert entity_id == "test-entity" + + retrieved_entity = await knowledge_source.get_entity("test-entity") + assert retrieved_entity is not None + assert retrieved_entity.id == "test-entity" + + # Test search + search_results = await knowledge_source.search_entities("test") + assert len(search_results) > 0 + + # Test relation + relation = KnowledgeRelation( + source_id="entity1", + target_id="entity2", + type="related_to" + ) + result = await knowledge_source.add_relation(relation) + assert result is True + +async def test_agent_factory_graphiti(): + """Test that the agent factory correctly creates and injects the GraphitiKnowledgeSource""" + os.environ["KNOWLEDGE_BACKEND"] = "graphiti" + factory = AgentFactory() + agent = factory.create_agent(DummyAgent, agent_id="test1") + + assert agent.knowledge is not None + assert isinstance(agent.knowledge, GraphitiKnowledgeSource) diff --git a/agent/run_autoflake.py b/agent/run_autoflake.py new file mode 100644 index 0000000..a37e8ea --- /dev/null +++ b/agent/run_autoflake.py @@ -0,0 +1,31 @@ +""" +Run autoflake on the entire backend codebase to remove unused imports and variables. +""" + +import os +import subprocess +from pathlib import Path + +def main(): + # Get the current directory (agent) + agent_dir = Path(__file__).parent + + # Get the mcp_agent directory + mcp_agent_dir = agent_dir / "mcp_agent" + + # Run autoflake on the mcp_agent directory + cmd = [ + "poetry", "run", "autoflake", + "--remove-all-unused-imports", + "--remove-unused-variables", + "--recursive", + "--in-place", + str(mcp_agent_dir) + ] + + print("Running autoflake on the backend code...") + subprocess.run(cmd, cwd=agent_dir) + print("Autoflake completed.") + +if __name__ == "__main__": + main() diff --git a/agent/test_knowledge_server.py b/agent/test_knowledge_server.py new file mode 100644 index 0000000..0f8af14 --- /dev/null +++ b/agent/test_knowledge_server.py @@ -0,0 +1,79 @@ +""" +Test script for the knowledge server. +This script tests the basic functionality of the knowledge server. +""" + +import asyncio +import json +from mcp_agent.integrations.graphiti_integration import ( + GraphitiKnowledgeSource, + KnowledgeEntity, + KnowledgeRelation +) + +async def test_knowledge_source(): + """Test the basic functionality of the GraphitiKnowledgeSource.""" + print("Testing GraphitiKnowledgeSource...") + + # Initialize the knowledge source + knowledge_source = GraphitiKnowledgeSource() + + # Test entity operations + print("\n1. Testing entity operations:") + + # Create a test entity + entity = KnowledgeEntity( + id="test-entity-1", + type="concept", + properties={"name": "Test Entity", "description": "This is a test entity"} + ) + + # Add the entity + entity_id = await knowledge_source.add_entity(entity) + print(f" - Added entity with ID: {entity_id}") + + # Get the entity + retrieved_entity = await knowledge_source.get_entity(entity_id) + print(f" - Retrieved entity: {retrieved_entity.dict()}") + + # Test relation operations + print("\n2. Testing relation operations:") + + # Create another test entity + entity2 = KnowledgeEntity( + id="test-entity-2", + type="concept", + properties={"name": "Related Entity", "description": "This is a related entity"} + ) + + # Add the second entity + entity2_id = await knowledge_source.add_entity(entity2) + print(f" - Added second entity with ID: {entity2_id}") + + # Create a relation between the entities + relation = KnowledgeRelation( + source_id=entity_id, + target_id=entity2_id, + type="related_to", + properties={"strength": 0.8} + ) + + # Add the relation + result = await knowledge_source.add_relation(relation) + print(f" - Added relation: {result}") + + # Test query operations + print("\n3. Testing query operations:") + + # Query the knowledge graph + query_result = await knowledge_source.query("test") + print(f" - Query result: {query_result.dict()}") + + # Search for entities + search_results = await knowledge_source.search_entities("Test") + print(f" - Search results: {[entity.dict() for entity in search_results]}") + + print("\nAll tests completed successfully!") + +if __name__ == "__main__": + asyncio.run(test_knowledge_source()) diff --git a/check-lint.js b/check-lint.js new file mode 100644 index 0000000..0a0c824 --- /dev/null +++ b/check-lint.js @@ -0,0 +1,14 @@ +const { execSync } = require('child_process'); +const path = require('path'); + +try { + console.log('Running ESLint check on frontend...'); + execSync('cd frontend && pnpm lint', { + stdio: 'inherit', + cwd: path.resolve(__dirname) + }); + console.log('ESLint completed successfully!'); +} catch (error) { + console.error('Error running ESLint:', error.message); + process.exit(1); +} diff --git a/fix-lint.js b/fix-lint.js new file mode 100644 index 0000000..c2a1675 --- /dev/null +++ b/fix-lint.js @@ -0,0 +1,14 @@ +const { execSync } = require('child_process'); +const path = require('path'); + +try { + console.log('Running ESLint fix on frontend...'); + execSync('cd frontend && npx eslint --fix "src/**/*.{ts,tsx}"', { + stdio: 'inherit', + cwd: path.resolve(__dirname) + }); + console.log('ESLint fix completed successfully!'); +} catch (error) { + console.error('Error running ESLint fix:', error.message); + process.exit(1); +} diff --git a/frontend/.vscode/settings.json b/frontend/.vscode/settings.json new file mode 100644 index 0000000..54f1258 --- /dev/null +++ b/frontend/.vscode/settings.json @@ -0,0 +1,14 @@ +{ + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "eslint.validate": [ + "javascript", + "javascriptreact", + "typescript", + "typescriptreact" + ], + "typescript.tsdk": "node_modules/typescript/lib", + "typescript.enablePromptUseWorkspaceTsdk": true +} diff --git a/frontend/lint-fix.js b/frontend/lint-fix.js new file mode 100644 index 0000000..062d779 --- /dev/null +++ b/frontend/lint-fix.js @@ -0,0 +1,15 @@ +const { execSync } = require('child_process'); +const path = require('path'); + +// Run ESLint on all TypeScript and TypeScript React files +try { + console.log('Running ESLint on all TypeScript files...'); + execSync('npx eslint --fix "src/**/*.{ts,tsx}"', { + stdio: 'inherit', + cwd: path.resolve(__dirname) + }); + console.log('ESLint completed successfully!'); +} catch (error) { + console.error('Error running ESLint:', error.message); + process.exit(1); +} diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico new file mode 100644 index 0000000..62a45f2 --- /dev/null +++ b/frontend/public/favicon.ico @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/logo.svg b/frontend/public/logo.svg new file mode 100644 index 0000000..936440e --- /dev/null +++ b/frontend/public/logo.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/map-marker.svg b/frontend/public/map-marker.svg new file mode 100644 index 0000000..d2c47aa --- /dev/null +++ b/frontend/public/map-marker.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/frontend/public/map-overlay.svg b/frontend/public/map-overlay.svg new file mode 100644 index 0000000..d2267e4 --- /dev/null +++ b/frontend/public/map-overlay.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/app/settings/layout.tsx b/frontend/src/app/settings/layout.tsx new file mode 100644 index 0000000..c4b2cd8 --- /dev/null +++ b/frontend/src/app/settings/layout.tsx @@ -0,0 +1,11 @@ +export default function SettingsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
{children}
+
+ ); +} diff --git a/frontend/src/app/settings/page.tsx b/frontend/src/app/settings/page.tsx new file mode 100644 index 0000000..aacd42b --- /dev/null +++ b/frontend/src/app/settings/page.tsx @@ -0,0 +1,280 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useSettings } from "@/providers/SettingsProvider"; +import { + Key, + Save, + Moon, + Sun, + Bug, + AlertCircle, + Eye, + EyeOff, + ArrowLeft +} from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Separator } from "@/components/ui/separator"; +import { Checkbox } from "@/components/ui/checkbox"; +import Link from "next/link"; + +export default function SettingsPage() { + const { settings, updateSettings, isLoaded } = useSettings(); + const [localSettings, setLocalSettings] = useState(settings); + const [showOpenAIKey, setShowOpenAIKey] = useState(false); + const [showLangsmithKey, setShowLangsmithKey] = useState(false); + const [showCopilotKey, setShowCopilotKey] = useState(false); + const [showGraphitiKey, setShowGraphitiKey] = useState(false); + const [isSaving, setIsSaving] = useState(false); + const [saveMessage, setSaveMessage] = useState(""); + + // Update local settings when global settings change + useEffect(() => { + if (isLoaded) { + setLocalSettings(settings); + } + }, [settings, isLoaded]); + + // Update API key + const updateApiKey = (key: keyof typeof localSettings.apiKeys, value: string) => { + setLocalSettings({ + ...localSettings, + apiKeys: { + ...localSettings.apiKeys, + [key]: value, + }, + }); + }; + + // Update feature flag + const updateFeatureFlag = (flag: keyof typeof localSettings.featureFlags, value: boolean) => { + setLocalSettings({ + ...localSettings, + featureFlags: { + ...localSettings.featureFlags, + [flag]: value, + }, + }); + }; + + // Save settings + const saveSettings = () => { + setIsSaving(true); + updateSettings(localSettings); + + // Show success message + setSaveMessage("Settings saved successfully!"); + setTimeout(() => { + setSaveMessage(""); + setIsSaving(false); + }, 2000); + }; + + if (!isLoaded) { + return ( +
+
+
+ ); + } + + return ( +
+
+ + + +

Application Settings

+
+ + {saveMessage && ( +
+ {saveMessage} +
+ )} + +
+
+
+

+ + API Keys +

+

+ Configure API keys for various services used by the application. + These keys are stored locally in your browser and are not sent to any server. +

+ +
+
+ + updateApiKey('openaiApiKey', e.target.value)} + placeholder="sk-..." + /> +

+ Required for the backend agent functionality +

+
+ +
+ + updateApiKey('langsmithApiKey', e.target.value)} + placeholder="ls-..." + /> +

+ Optional for LangSmith tracing and monitoring +

+
+ +
+ + updateApiKey('copilotCloudApiKey', e.target.value)} + placeholder="cpk-..." + /> +

+ Required for the frontend chat functionality +

+
+ +
+ + updateApiKey('graphitiApiKey', e.target.value)} + placeholder="gft-..." + /> +

+ Optional for Knowledge Graph functionality +

+
+
+
+
+ +
+
+

+ + Features +

+

+ Configure application features and appearance. +

+ +
+
+ + updateFeatureFlag('enableDarkMode', checked === true) + } + /> + +
+ +
+ + updateFeatureFlag('enableDebugMode', checked === true) + } + /> + +
+
+
+ +
+
+ +

+ Some features are experimental and may not work as expected. + Changes to these settings may require a page refresh to take effect. +

+
+
+
+
+ +
+ +
+
+ ); +} diff --git a/frontend/src/components/agents/knowledge-agent.tsx b/frontend/src/components/agents/knowledge-agent.tsx new file mode 100644 index 0000000..48581ef --- /dev/null +++ b/frontend/src/components/agents/knowledge-agent.tsx @@ -0,0 +1,133 @@ +import React, { FC, useState, useEffect, useRef } from "react"; +import { AvailableAgents } from "@/lib/available-agents"; +import { useCoAgent, useCoAgentStateRender } from "@copilotkit/react-core"; +import { CheckCircleIcon } from "lucide-react"; +import { KnowledgeGraph } from "@/components/knowledge-graph"; + +export type KnowledgeAgentState = { + query: string; + entities: Array<{ + id: string; + name: string; + type: string; + properties: Record; + }>; + relations: Array<{ + source_id: string; + target_id: string; + type: string; + properties?: Record; + }>; + logs: Array<{ + message: string; + done: boolean; + }>; +}; + +export const KnowledgeAgent: FC = () => { + const [logs, setLogs] = useState< + Array<{ + message: string; + done: boolean; + }> + >([]); + + const isProcessing = useRef(false); + + const { state: knowledgeAgentState, stop: stopKnowledgeAgent } = useCoAgent({ + name: AvailableAgents.KNOWLEDGE_AGENT, + initialState: { + query: "", + entities: [], + relations: [], + logs: [], + }, + }); + + useEffect(() => { + if (knowledgeAgentState.logs) { + setLogs((prevLogs) => { + const newLogs = [...prevLogs]; + knowledgeAgentState.logs.forEach((log) => { + const existingLogIndex = newLogs.findIndex( + (l) => l.message === log.message + ); + if (existingLogIndex >= 0) { + if (log.done && !newLogs[existingLogIndex].done) { + newLogs[existingLogIndex].done = true; + } + } else { + newLogs.push(log); + } + }); + return newLogs; + }); + } + }, [knowledgeAgentState.logs]); + + useCoAgentStateRender({ + name: AvailableAgents.KNOWLEDGE_AGENT, + handler: ({ nodeName }) => { + if (nodeName === "__end__") { + setTimeout(() => { + stopKnowledgeAgent(); + }, 1000); + } + }, + render: ({ status }) => { + if (status === "inProgress") { + isProcessing.current = true; + return ( +
+

Processing your knowledge query...

+
    + {logs.map((log, idx) => ( +
  • + + {log.done ? "✓" : "⟳"} + + {log.message} +
  • + ))} +
+
+ ); + } + + if (status === "complete") { + isProcessing.current = false; + return ( +
+
+
+ + Knowledge query complete +
+
+
+ ); + } + }, + }); + + if (isProcessing.current) { + return ( +
+
+
+
+
+
+
+
+ ); + } + + return ( +
+
+ +
+
+ ); +}; diff --git a/frontend/src/components/enhanced-layout.tsx b/frontend/src/components/enhanced-layout.tsx new file mode 100644 index 0000000..883975d --- /dev/null +++ b/frontend/src/components/enhanced-layout.tsx @@ -0,0 +1,46 @@ +"use client"; + +import React, { useState } from "react"; +import { EnhancedSidebar } from "./enhanced-sidebar"; +import { SidebarProvider } from "@/components/ui/sidebar"; +import { ThemeProvider } from "next-themes"; +import { MCPConfigModal } from "./mcp-config-modal"; +import { SettingsModal } from "./settings-modal"; +import { cn } from "@/lib/utils"; + +export function EnhancedLayout({ children }: { children: React.ReactNode }) { + const [showMCPConfigModal, setShowMCPConfigModal] = useState(false); + const [showSettingsModal, setShowSettingsModal] = useState(false); + + return ( + + +
+ setShowMCPConfigModal(true)} + onShowSettingsModal={() => setShowSettingsModal(true)} + /> + +
+ {children} +
+
+ + {/* Modals */} + setShowMCPConfigModal(false)} + /> + setShowSettingsModal(false)} + /> +
+
+ ); +} diff --git a/frontend/src/components/enhanced-sidebar.tsx b/frontend/src/components/enhanced-sidebar.tsx new file mode 100644 index 0000000..7074321 --- /dev/null +++ b/frontend/src/components/enhanced-sidebar.tsx @@ -0,0 +1,405 @@ +"use client"; + +import React, { useState, useEffect, ReactNode } from "react"; + +import { useCoAgent } from "@copilotkit/react-core"; +import { + MapPin, + BookOpen, + Server, + Network, + Settings, + Moon, + Sun, + Menu, + Home, + MessageSquare, + Github, + HelpCircle +} from "lucide-react"; +import Image from "next/image"; +import Link from "next/link"; +import { useTheme } from "next-themes"; + +import { Button } from "@/components/ui/button"; +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarGroup, + SidebarHeader, + SidebarMenuButton, + SidebarMenuItem, + SidebarMenu, + useSidebar +} from "@/components/ui/sidebar"; +import { AvailableAgents } from "@/lib/available-agents"; +import { cn } from "@/lib/utils"; + +// Custom SidebarMenuItem that accepts icon and isActive props +interface EnhancedSidebarMenuItemProps { + icon?: ReactNode; + isActive?: boolean; + className?: string; + children: ReactNode; +} + +function EnhancedSidebarMenuItem({ icon, isActive, className, children }: EnhancedSidebarMenuItemProps) { + return ( + +
+ {icon && {icon}} + {children} +
+
+ ); +} + +export function EnhancedSidebar({ + onShowMCPConfigModal, + onShowSettingsModal +}: { + showMCPConfigModal?: boolean; + showSettingsModal?: boolean; + onShowMCPConfigModal: () => void; + onShowSettingsModal: () => void; +}) { + const { theme, setTheme } = useTheme(); + const { setOpenMobile } = useSidebar(); + const [activeAgent, setActiveAgent] = useState(null); + const [isHovering, setIsHovering] = useState(null); + + // Get agent running states + const { running: travelAgentRunning } = useCoAgent({ + name: AvailableAgents.TRAVEL_AGENT, + }); + + const { running: researchAgentRunning } = useCoAgent({ + name: AvailableAgents.RESEARCH_AGENT, + }); + + const { running: mcpAgentRunning } = useCoAgent({ + name: AvailableAgents.MCP_AGENT, + }); + + const { running: knowledgeAgentRunning } = useCoAgent({ + name: AvailableAgents.KNOWLEDGE_AGENT, + }); + + // Update active agent based on running state + useEffect(() => { + if (travelAgentRunning) { + setActiveAgent(AvailableAgents.TRAVEL_AGENT); + } else if (researchAgentRunning) { + setActiveAgent(AvailableAgents.RESEARCH_AGENT); + } else if (mcpAgentRunning) { + setActiveAgent(AvailableAgents.MCP_AGENT); + } else if (knowledgeAgentRunning) { + setActiveAgent(AvailableAgents.KNOWLEDGE_AGENT); + } else { + setActiveAgent(null); + } + }, [travelAgentRunning, researchAgentRunning, mcpAgentRunning, knowledgeAgentRunning]); + + // Handle theme toggle + const toggleTheme = () => { + setTheme(theme === "dark" ? "light" : "dark"); + }; + + // Get agent color class + const getAgentColorClass = (agent: AvailableAgents): string => { + switch (agent) { + case AvailableAgents.TRAVEL_AGENT: + return "text-[hsl(var(--agent-travel))]"; + case AvailableAgents.RESEARCH_AGENT: + return "text-[hsl(var(--agent-research))]"; + case AvailableAgents.MCP_AGENT: + return "text-[hsl(var(--agent-mcp))]"; + case AvailableAgents.KNOWLEDGE_AGENT: + return "text-[hsl(var(--agent-knowledge))]"; + default: + return "text-sidebar-foreground"; + } + }; + + return ( +
+ {/* Mobile menu trigger */} +
+ +
+ + + +
+
+ Multi-Agent Canvas +
+

+ Multi-Agent Canvas +

+
+
+ + + {/* Main Navigation */} + + + } + isActive={!activeAgent} + className="transition-all-fast hover-scale" + > + + Home + + + + +
+ + + Chat + +
+
+
+
+ + {/* Agents Section */} + +

+ Agents +

+ + {/* Travel Agent */} + +
+
setIsHovering(AvailableAgents.TRAVEL_AGENT)} + onMouseLeave={() => setIsHovering(null)} + > + + {travelAgentRunning && ( + + )} + {isHovering === AvailableAgents.TRAVEL_AGENT && !travelAgentRunning && ( +
+ )} +
+ + Travel Agent + {travelAgentRunning && ( + + )} + +
+ + + {/* Research Agent */} + +
+
setIsHovering(AvailableAgents.RESEARCH_AGENT)} + onMouseLeave={() => setIsHovering(null)} + > + + {researchAgentRunning && ( + + )} + {isHovering === AvailableAgents.RESEARCH_AGENT && !researchAgentRunning && ( +
+ )} +
+ + Research Agent + {researchAgentRunning && ( + + )} + +
+ + + {/* MCP Agent */} + +
+
setIsHovering(AvailableAgents.MCP_AGENT)} + onMouseLeave={() => setIsHovering(null)} + > + + {mcpAgentRunning && ( + + )} + {isHovering === AvailableAgents.MCP_AGENT && !mcpAgentRunning && ( +
+ )} +
+ + MCP Agent + {mcpAgentRunning && ( + + )} + +
+ + + {/* Knowledge Agent */} + +
+
setIsHovering(AvailableAgents.KNOWLEDGE_AGENT)} + onMouseLeave={() => setIsHovering(null)} + > + + {knowledgeAgentRunning && ( + + )} + {isHovering === AvailableAgents.KNOWLEDGE_AGENT && !knowledgeAgentRunning && ( +
+ )} +
+ + Knowledge Agent + {knowledgeAgentRunning && ( + + )} + +
+ + + + + {/* Configuration Section */} + +

+ Configuration +

+ + +
+ + MCP Servers +
+
+ +
+ + Settings +
+
+
+
+ + + + +
+ {/* Theme Toggle */} + + {theme === "dark" ? ( + + ) : ( + + )} + + + {/* Help Button */} + + + + + {/* GitHub Link */} + + + + + +
+ + {/* Version Info */} +
+ Version 0.1.0 +
+
+
+ +
+ ); +} diff --git a/frontend/src/components/entity-form.tsx b/frontend/src/components/entity-form.tsx new file mode 100644 index 0000000..8f76d8d --- /dev/null +++ b/frontend/src/components/entity-form.tsx @@ -0,0 +1,232 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { X, Plus, Save } from "lucide-react"; + +type EntityFormProps = { + isOpen: boolean; + onClose: () => void; + onSave: (entity: { + id: string; + name: string; + type: string; + properties: Record; + }) => void; + initialEntity?: { + id: string; + name: string; + type: string; + properties: Record; + }; +}; + +export const EntityForm: React.FC = ({ + isOpen, + onClose, + onSave, + initialEntity, +}) => { + const [entityId, setEntityId] = useState(""); + const [entityName, setEntityName] = useState(""); + const [entityType, setEntityType] = useState("concept"); + const [properties, setProperties] = useState>([ + { key: "", value: "" }, + ]); + + // Reset form when opened or when initialEntity changes + useEffect(() => { + if (isOpen) { + if (initialEntity) { + setEntityId(initialEntity.id); + setEntityName(initialEntity.name); + setEntityType(initialEntity.type); + + // Convert properties object to array of key-value pairs + const propsArray = Object.entries(initialEntity.properties).map(([key, value]) => ({ + key, + value: String(value), + })); + + setProperties(propsArray.length > 0 ? propsArray : [{ key: "", value: "" }]); + } else { + // Reset form for new entity + setEntityId(""); + setEntityName(""); + setEntityType("concept"); + setProperties([{ key: "", value: "" }]); + } + } + }, [isOpen, initialEntity]); + + const handleAddProperty = () => { + setProperties([...properties, { key: "", value: "" }]); + }; + + const handleRemoveProperty = (index: number) => { + const newProperties = [...properties]; + newProperties.splice(index, 1); + setProperties(newProperties.length > 0 ? newProperties : [{ key: "", value: "" }]); + }; + + const handlePropertyChange = (index: number, field: "key" | "value", value: string) => { + const newProperties = [...properties]; + newProperties[index][field] = value; + setProperties(newProperties); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + // Convert properties array to object + const propertiesObject = properties.reduce((acc, { key, value }) => { + if (key.trim()) { + acc[key.trim()] = value; + } + return acc; + }, {} as Record); + + onSave({ + id: entityId || `entity-${Date.now()}`, + name: entityName, + type: entityType, + properties: propertiesObject, + }); + + onClose(); + }; + + if (!isOpen) return null; + + return ( +
+
+
+

+ {initialEntity ? "Edit Entity" : "Add New Entity"} +

+ +
+ +
+
+
+ + setEntityId(e.target.value)} + className="w-full px-3 py-2 border rounded-md text-sm" + placeholder="e.g., person-123 (auto-generated if empty)" + disabled={!!initialEntity} + /> +
+ +
+ + setEntityName(e.target.value)} + className="w-full px-3 py-2 border rounded-md text-sm" + placeholder="e.g., John Doe" + required + /> +
+ +
+ + +
+ +
+
+ + +
+ +
+ {properties.map((prop, index) => ( +
+ + handlePropertyChange(index, "key", e.target.value) + } + className="w-1/3 px-3 py-2 border rounded-md text-sm" + placeholder="Key" + /> + + handlePropertyChange(index, "value", e.target.value) + } + className="flex-1 px-3 py-2 border rounded-md text-sm" + placeholder="Value" + /> + +
+ ))} +
+
+ +
+ + +
+
+
+
+
+ ); +}; diff --git a/frontend/src/components/knowledge-graph.tsx b/frontend/src/components/knowledge-graph.tsx new file mode 100644 index 0000000..7ae16d1 --- /dev/null +++ b/frontend/src/components/knowledge-graph.tsx @@ -0,0 +1,390 @@ +"use client"; + +import type { FC} from "react"; +import React, { useEffect, useState } from "react"; + +import { useCoAgent } from "@copilotkit/react-core"; +import { Search, Plus, RotateCw, Info, Filter } from "lucide-react"; + +import { AvailableAgents } from "@/lib/available-agents"; + +import { EntityForm } from "./entity-form"; + +type GraphNode = { + id: string; + name: string; + type: string; + val: number; // Size of node + color?: string; + properties: Record; +}; + +type GraphLink = { + source: string; + target: string; + type: string; + properties?: Record; +}; + +type GraphData = { + nodes: GraphNode[]; + links: GraphLink[]; +}; + +export const KnowledgeGraph: FC = () => { + const [graphData, setGraphData] = useState({ nodes: [], links: [] }); + const [searchQuery, setSearchQuery] = useState(""); + const [selectedNode, setSelectedNode] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [showEntityForm, setShowEntityForm] = useState(false); + const [entityToEdit, setEntityToEdit] = useState(null); + // No longer need graphRef since we're not using ForceGraph2D + // const graphRef = useRef(null); + + // Get MCP agent state to access knowledge graph data + useCoAgent({ + name: AvailableAgents.MCP_AGENT, + }); + + // Function to handle node click + const handleNodeClick = (node: GraphNode) => { + setSelectedNode(node); + }; + + // Function to add a new entity + const addEntity = () => { + setEntityToEdit(null); + setShowEntityForm(true); + }; + + // Function to edit an entity + const editEntity = (node: GraphNode) => { + setEntityToEdit(node); + setShowEntityForm(true); + }; + + // Function to save an entity + const saveEntity = (entity: { + id: string; + name: string; + type: string; + properties: Record; + }) => { + // In a real implementation, this would call the MCP agent to save the entity + console.log("Saving entity:", entity); + + // For now, just update the local graph data + setGraphData(prevData => { + const newNodes = [...prevData.nodes]; + const existingNodeIndex = newNodes.findIndex(node => node.id === entity.id); + + if (existingNodeIndex >= 0) { + // Update existing node + newNodes[existingNodeIndex] = { + ...newNodes[existingNodeIndex], + name: entity.name, + type: entity.type, + properties: entity.properties, + }; + } else { + // Add new node + newNodes.push({ + id: entity.id, + name: entity.name, + type: entity.type, + val: 5, // Default size + color: getColorForType(entity.type), + properties: entity.properties, + }); + } + + return { + nodes: newNodes, + links: prevData.links, + }; + }); + }; + + // Helper function to get color based on entity type + const getColorForType = (type: string): string => { + const typeColors: Record = { + concept: "#4CAF50", + person: "#2196F3", + organization: "#FF9800", + location: "#9C27B0", + event: "#F44336", + custom: "#607D8B", + }; + + return typeColors[type] || "#607D8B"; + }; + + // Function to search the knowledge graph + const searchGraph = () => { + if (!searchQuery.trim()) return; + + setIsLoading(true); + + // This is a placeholder - in a real implementation, you would: + // 1. Call the MCP agent with a search query + // 2. Process the results and update the graph + + // Simulate loading + setTimeout(() => { + // Example data - in a real implementation, this would come from the MCP agent + const newData: GraphData = { + nodes: [ + { id: "1", name: "Concept A", type: "concept", val: 5, color: "#4CAF50", properties: {} }, + { id: "2", name: "Concept B", type: "concept", val: 3, color: "#4CAF50", properties: {} }, + { id: "3", name: "Entity X", type: "entity", val: 4, color: "#2196F3", properties: {} }, + { id: "4", name: "Entity Y", type: "entity", val: 2, color: "#2196F3", properties: {} }, + { id: "5", name: searchQuery, type: "search", val: 6, color: "#FFC107", properties: {} }, + ], + links: [ + { source: "5", target: "1", type: "related_to" }, + { source: "5", target: "3", type: "related_to" }, + { source: "1", target: "2", type: "similar_to" }, + { source: "3", target: "4", type: "part_of" }, + { source: "2", target: "4", type: "related_to" }, + ], + }; + + setGraphData(newData); + setIsLoading(false); + }, 1000); + }; + + // Function to reset the graph view (removed duplicate) + + // Initialize with empty graph + useEffect(() => { + // In a real implementation, you might load an initial graph state here + setGraphData({ + nodes: [], + links: [], + }); + }, []); + + // Function to reset view (simplified for card-based view) + const resetView = () => { + // In a card-based view, this could scroll to top or reset filters + window.scrollTo(0, 0); + }; + + return ( +
+ {/* Entity Form Modal */} + setShowEntityForm(false)} + onSave={saveEntity} + initialEntity={entityToEdit || undefined} + /> + {/* Header and controls */} +
+
+

Knowledge Graph

+
+ {graphData.nodes.length} nodes +
+
+
+ + +
+
+ + {/* Search and add controls */} +
+
+ setSearchQuery(e.target.value)} + placeholder="Search knowledge graph..." + className="w-full px-1.5 py-0.5 text-xs border rounded-md pr-6" + onKeyDown={(e) => e.key === 'Enter' && searchGraph()} + /> + +
+ +
+ + {/* Filters and advanced controls */} +
+
+ + Filters: +
+
+ + + +
+
+ + {/* Graph visualization */} +
+ {isLoading && ( +
+
+
+ )} + + {graphData.nodes.length === 0 && !isLoading ? ( +
+
+

No data to display

+

Search for concepts or entities to visualize the knowledge graph

+
+
+ ) : ( +
+
+ {graphData.nodes.map((node) => ( +
handleNodeClick(node)} + > +
+
+

{node.name}

+
+
+ {node.type} +
+ {node.properties && Object.keys(node.properties).length > 0 && ( +
+ {Object.entries(node.properties) + .slice(0, 1) + .map(([key, value]) => ( +
+ {key}: {String(value).substring(0, 15)}{String(value).length > 15 ? '...' : ''} +
+ ))} + {Object.keys(node.properties).length > 1 && ( +
+{Object.keys(node.properties).length - 1} more
+ )} +
+ )} +
+ ))} +
+ + {graphData.nodes.length > 0 && ( +
+

Relationships

+
+ {graphData.links.map((link, index) => ( +
+
+ {graphData.nodes.find(n => n.id === link.source)?.name || link.source} +
+
+ {link.type} +
+
+ {graphData.nodes.find(n => n.id === link.target)?.name || link.target} +
+
+ ))} + {graphData.links.length === 0 && ( +
+ No relationships found +
+ )} +
+
+ )} +
+ )} +
+ + {/* Selected node details */} + {selectedNode && ( +
+
+

{selectedNode.name}

+ + {selectedNode.type} + +
+ +
+

Properties

+
+ {selectedNode.properties && Object.entries(selectedNode.properties).length > 0 ? ( +
+ {Object.entries(selectedNode.properties).map(([key, value]) => ( + +
{key}
+
{String(value)}
+
+ ))} +
+ ) : ( +

No properties available

+ )} +
+
+ +
+ + + +
+
+ )} +
+ ); +}; diff --git a/frontend/src/components/settings-modal.tsx b/frontend/src/components/settings-modal.tsx new file mode 100644 index 0000000..f3ebb28 --- /dev/null +++ b/frontend/src/components/settings-modal.tsx @@ -0,0 +1,279 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { useLocalStorage } from "@/hooks/use-local-storage"; +import { + AppSettings, + DEFAULT_SETTINGS, + SETTINGS_STORAGE_KEY +} from "@/lib/settings-types"; +import { + Key, + Save, + X, + Moon, + Sun, + Bug, + AlertCircle, + Eye, + EyeOff +} from "lucide-react"; +import { cn } from "@/lib/utils"; + +// UI Components +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Separator } from "@/components/ui/separator"; +import { Checkbox } from "@/components/ui/checkbox"; + +interface SettingsModalProps { + isOpen: boolean; + onClose: () => void; +} + +export function SettingsModal({ isOpen, onClose }: SettingsModalProps) { + // Get settings from local storage + const [savedSettings, setSavedSettings] = useLocalStorage( + SETTINGS_STORAGE_KEY, + DEFAULT_SETTINGS + ); + + // Local state for form + const [settings, setSettings] = useState(DEFAULT_SETTINGS); + const [isLoading, setIsLoading] = useState(true); + const [activeTab, setActiveTab] = useState<'api-keys' | 'features'>('api-keys'); + const [showOpenAIKey, setShowOpenAIKey] = useState(false); + const [showLangsmithKey, setShowLangsmithKey] = useState(false); + const [showCopilotKey, setShowCopilotKey] = useState(false); + const [showGraphitiKey, setShowGraphitiKey] = useState(false); + + // Load settings from local storage + useEffect(() => { + if (savedSettings) { + setSettings(savedSettings); + } + setIsLoading(false); + }, [savedSettings]); + + // Save settings + const saveSettings = () => { + setSavedSettings(settings); + onClose(); + }; + + // Update API key + const updateApiKey = (key: keyof AppSettings['apiKeys'], value: string) => { + setSettings({ + ...settings, + apiKeys: { + ...settings.apiKeys, + [key]: value, + }, + }); + }; + + // Update feature flag + const updateFeatureFlag = (flag: keyof AppSettings['featureFlags'], value: boolean) => { + setSettings({ + ...settings, + featureFlags: { + ...settings.featureFlags, + [flag]: value, + }, + }); + }; + + if (isLoading) { + return null; + } + + return ( + !open && onClose()}> + + + Application Settings + + Configure API keys and application settings + + + + {/* Tabs */} +
+ + +
+ + {/* API Keys Tab */} + {activeTab === 'api-keys' && ( +
+
+ + updateApiKey('openaiApiKey', e.target.value)} + placeholder="sk-..." + /> +

+ Required for the backend agent functionality +

+
+ +
+ + updateApiKey('langsmithApiKey', e.target.value)} + placeholder="ls-..." + /> +

+ Optional for LangSmith tracing and monitoring +

+
+ +
+ + updateApiKey('copilotCloudApiKey', e.target.value)} + placeholder="cpk-..." + /> +

+ Required for the frontend chat functionality +

+
+ +
+ + updateApiKey('graphitiApiKey', e.target.value)} + placeholder="gft-..." + /> +

+ Optional for Knowledge Graph functionality +

+
+
+ )} + + {/* Features Tab */} + {activeTab === 'features' && ( +
+
+ + updateFeatureFlag('enableDarkMode', checked === true) + } + /> + +
+ +
+ + updateFeatureFlag('enableDebugMode', checked === true) + } + /> + +
+ +
+
+ +

+ Some features are experimental and may not work as expected. + Changes to these settings may require a page refresh to take effect. +

+
+
+
+ )} + + + + + +
+
+ ); +} diff --git a/frontend/src/components/ui/loading-animation.tsx b/frontend/src/components/ui/loading-animation.tsx new file mode 100644 index 0000000..c643371 --- /dev/null +++ b/frontend/src/components/ui/loading-animation.tsx @@ -0,0 +1,190 @@ +"use client"; + +import React from "react"; +import { cn } from "@/lib/utils"; + +export function LoadingAnimation({ + className, + size = "default", +}: { + className?: string; + size?: "sm" | "default" | "lg"; +}) { + const sizeClasses = { + sm: "w-4 h-4", + default: "w-8 h-8", + lg: "w-12 h-12", + }; + + return ( +
+
+
+
+
+ + + +
+ ); +} + +export function LoadingDots({ + className, + size = "default", + color = "primary", +}: { + className?: string; + size?: "sm" | "default" | "lg"; + color?: "primary" | "secondary" | "accent" | "muted"; +}) { + const sizeClasses = { + sm: "w-1 h-1 mx-0.5", + default: "w-2 h-2 mx-1", + lg: "w-3 h-3 mx-1.5", + }; + + const colorClasses = { + primary: "bg-primary", + secondary: "bg-secondary", + accent: "bg-accent", + muted: "bg-muted-foreground", + }; + + return ( +
+
+
+
+
+ ); +} + +export function LoadingPulse({ + className, + width = "w-full", + height = "h-8", +}: { + className?: string; + width?: string; + height?: string; +}) { + return ( +
+ ); +} + +export function LoadingSpinner({ + className, + size = "default", +}: { + className?: string; + size?: "sm" | "default" | "lg"; +}) { + const sizeClasses = { + sm: "w-4 h-4", + default: "w-6 h-6", + lg: "w-8 h-8", + }; + + return ( + + + + + ); +} + +export function LoadingProgress({ + className, + value = 0, + indeterminate = false, +}: { + className?: string; + value?: number; + indeterminate?: boolean; +}) { + return ( +
+
+
+ ); +} + +export function LoadingCard({ + className, + rows = 3, +}: { + className?: string; + rows?: number; +}) { + return ( +
+ + {Array.from({ length: rows }).map((_, i) => ( + + ))} +
+ ); +} diff --git a/frontend/src/lib/settings-types.ts b/frontend/src/lib/settings-types.ts new file mode 100644 index 0000000..5abe087 --- /dev/null +++ b/frontend/src/lib/settings-types.ts @@ -0,0 +1,38 @@ +// Settings types for the application + +// Local storage key for saving settings +export const SETTINGS_STORAGE_KEY = "app-settings"; + +// API Keys configuration +export interface ApiKeys { + openaiApiKey?: string; + langsmithApiKey?: string; + copilotCloudApiKey?: string; + graphitiApiKey?: string; +} + +// Feature flags +export interface FeatureFlags { + enableDarkMode?: boolean; + enableDebugMode?: boolean; +} + +// Application settings +export interface AppSettings { + apiKeys: ApiKeys; + featureFlags: FeatureFlags; +} + +// Default settings +export const DEFAULT_SETTINGS: AppSettings = { + apiKeys: { + openaiApiKey: "", + langsmithApiKey: "", + copilotCloudApiKey: "", + graphitiApiKey: "", + }, + featureFlags: { + enableDarkMode: false, + enableDebugMode: false, + }, +}; diff --git a/frontend/src/providers/SettingsProvider.tsx b/frontend/src/providers/SettingsProvider.tsx new file mode 100644 index 0000000..db5872e --- /dev/null +++ b/frontend/src/providers/SettingsProvider.tsx @@ -0,0 +1,94 @@ +"use client"; + +import React, { createContext, useContext, useEffect, useState } from "react"; +import { useLocalStorage } from "@/hooks/use-local-storage"; +import { + AppSettings, + DEFAULT_SETTINGS, + SETTINGS_STORAGE_KEY +} from "@/lib/settings-types"; + +// Create context +type SettingsContextType = { + settings: AppSettings; + updateSettings: (newSettings: AppSettings) => void; + updateApiKey: (key: keyof AppSettings['apiKeys'], value: string) => void; + updateFeatureFlag: (flag: keyof AppSettings['featureFlags'], value: boolean) => void; + isLoaded: boolean; +}; + +const SettingsContext = createContext(undefined); + +// Provider component +export function SettingsProvider({ children }: { children: React.ReactNode }) { + const [savedSettings, setSavedSettings] = useLocalStorage( + SETTINGS_STORAGE_KEY, + DEFAULT_SETTINGS + ); + + const [settings, setSettings] = useState(DEFAULT_SETTINGS); + const [isLoaded, setIsLoaded] = useState(false); + + // Load settings from local storage + useEffect(() => { + if (savedSettings) { + setSettings(savedSettings); + } + setIsLoaded(true); + }, [savedSettings]); + + // Update entire settings object + const updateSettings = (newSettings: AppSettings) => { + setSettings(newSettings); + setSavedSettings(newSettings); + }; + + // Update a specific API key + const updateApiKey = (key: keyof AppSettings['apiKeys'], value: string) => { + const newSettings = { + ...settings, + apiKeys: { + ...settings.apiKeys, + [key]: value, + }, + }; + setSettings(newSettings); + setSavedSettings(newSettings); + }; + + // Update a specific feature flag + const updateFeatureFlag = (flag: keyof AppSettings['featureFlags'], value: boolean) => { + const newSettings = { + ...settings, + featureFlags: { + ...settings.featureFlags, + [flag]: value, + }, + }; + setSettings(newSettings); + setSavedSettings(newSettings); + }; + + return ( + + {children} + + ); +} + +// Hook for using the settings context +export function useSettings() { + const context = useContext(SettingsContext); + if (context === undefined) { + throw new Error("useSettings must be used within a SettingsProvider"); + } + return context; +} diff --git a/frontend/src/styles/theme.css b/frontend/src/styles/theme.css new file mode 100644 index 0000000..b96d220 --- /dev/null +++ b/frontend/src/styles/theme.css @@ -0,0 +1,129 @@ +/* Enhanced theme for Open Multi-Agent Canvas */ + +:root { + /* Primary brand colors */ + --brand-primary: 220 70% 50%; /* Vibrant blue */ + --brand-secondary: 280 60% 60%; /* Purple */ + --brand-accent: 160 70% 45%; /* Teal */ + --brand-neutral: 220 15% 20%; /* Dark blue-gray */ + + /* UI colors */ + --ui-background: 0 0% 100%; + --ui-foreground: 220 15% 20%; + --ui-card: 0 0% 100%; + --ui-card-foreground: 220 15% 20%; + --ui-popover: 0 0% 100%; + --ui-popover-foreground: 220 15% 20%; + --ui-primary: 220 70% 50%; + --ui-primary-foreground: 0 0% 100%; + --ui-secondary: 280 60% 60%; + --ui-secondary-foreground: 0 0% 100%; + --ui-muted: 220 15% 95%; + --ui-muted-foreground: 220 15% 40%; + --ui-accent: 160 70% 45%; + --ui-accent-foreground: 0 0% 100%; + --ui-destructive: 0 70% 50%; + --ui-destructive-foreground: 0 0% 100%; + --ui-border: 220 15% 90%; + --ui-input: 220 15% 90%; + --ui-ring: 220 70% 50%; + + /* Sidebar specific colors */ + --sidebar-background: 220 15% 20%; + --sidebar-foreground: 0 0% 100%; + --sidebar-primary: 220 70% 50%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 160 70% 45%; + --sidebar-accent-foreground: 0 0% 100%; + --sidebar-border: 220 15% 30%; + --sidebar-ring: 220 70% 50%; + + /* Agent specific colors */ + --agent-travel: 200 70% 50%; + --agent-research: 280 60% 60%; + --agent-mcp: 340 70% 50%; + --agent-knowledge: 160 70% 45%; + + /* Animation durations */ + --animation-fast: 150ms; + --animation-normal: 250ms; + --animation-slow: 350ms; + + /* Shadows */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1); + --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15); +} + +.dark { + /* UI colors for dark mode */ + --ui-background: 220 15% 10%; + --ui-foreground: 0 0% 95%; + --ui-card: 220 15% 15%; + --ui-card-foreground: 0 0% 95%; + --ui-popover: 220 15% 15%; + --ui-popover-foreground: 0 0% 95%; + --ui-primary: 220 70% 50%; + --ui-primary-foreground: 0 0% 100%; + --ui-secondary: 280 60% 60%; + --ui-secondary-foreground: 0 0% 100%; + --ui-muted: 220 15% 20%; + --ui-muted-foreground: 220 15% 70%; + --ui-accent: 160 70% 45%; + --ui-accent-foreground: 0 0% 100%; + --ui-destructive: 0 70% 50%; + --ui-destructive-foreground: 0 0% 100%; + --ui-border: 220 15% 25%; + --ui-input: 220 15% 25%; + --ui-ring: 220 70% 50%; + + /* Sidebar specific colors for dark mode */ + --sidebar-background: 220 15% 15%; + --sidebar-foreground: 0 0% 95%; + --sidebar-primary: 220 70% 50%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 160 70% 45%; + --sidebar-accent-foreground: 0 0% 100%; + --sidebar-border: 220 15% 25%; + --sidebar-ring: 220 70% 50%; +} + +/* Animation keyframes */ +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes slideInLeft { + from { transform: translateX(-20px); opacity: 0; } + to { transform: translateX(0); opacity: 1; } +} + +@keyframes slideInRight { + from { transform: translateX(20px); opacity: 0; } + to { transform: translateX(0); opacity: 1; } +} + +@keyframes pulse { + 0% { opacity: 0.6; } + 50% { opacity: 1; } + 100% { opacity: 0.6; } +} + +/* Utility classes */ +.animate-fade-in { + animation: fadeIn var(--animation-normal) ease-in-out; +} + +.animate-slide-in-left { + animation: slideInLeft var(--animation-normal) ease-in-out; +} + +.animate-slide-in-right { + animation: slideInRight var(--animation-normal) ease-in-out; +} + +.animate-pulse-subtle { + animation: pulse 2s infinite ease-in-out; +} diff --git a/install-agent.bat b/install-agent.bat new file mode 100644 index 0000000..9b50d90 --- /dev/null +++ b/install-agent.bat @@ -0,0 +1,9 @@ +@echo off +REM Install the agent package in development mode + +cd /d %~dp0agent +poetry install +poetry run pip install -e . + +echo Agent package installed in development mode. +pause diff --git a/install-autoflake.bat b/install-autoflake.bat new file mode 100644 index 0000000..5e92d42 --- /dev/null +++ b/install-autoflake.bat @@ -0,0 +1,4 @@ +@echo off +cd agent +poetry add --dev autoflake +pause diff --git a/run-build.js b/run-build.js new file mode 100644 index 0000000..1e88634 --- /dev/null +++ b/run-build.js @@ -0,0 +1,14 @@ +const { execSync } = require('child_process'); +const path = require('path'); + +try { + console.log('Running build process for frontend...'); + execSync('cd frontend && pnpm build', { + stdio: 'inherit', + cwd: path.resolve(__dirname) + }); + console.log('Build completed successfully!'); +} catch (error) { + console.error('Error running build:', error.message); + process.exit(1); +} diff --git a/test-langgraph.bat b/test-langgraph.bat new file mode 100644 index 0000000..fe52fd4 --- /dev/null +++ b/test-langgraph.bat @@ -0,0 +1,4 @@ +@echo off +cd agent +poetry run langgraph dev --config langgraph.json --host localhost --port 8123 --no-browser +pause From 18402c73dd133de4dc41ec93bc4c77d242d296dd Mon Sep 17 00:00:00 2001 From: kb01111 Date: Wed, 23 Apr 2025 19:16:18 +0200 Subject: [PATCH 2/5] Fix LangGraph integration and improve server reliability --- {frontend => .cursor}/.cursorrules | 0 .cursor/rules/agent-best-practices.mdc | 39 ++ .cursor/rules/context7-mcp-tool.mdc | 26 + .cursor/rules/frontend-best-practices.mdc | 46 ++ .cursor/rules/fullstack-best-practices.mdc | 43 ++ .idea/AugmentWebviewStateStore.xml | 2 +- .idea/KB-multi-agent.iml | 12 +- .idea/misc.xml | 5 +- README.md | 219 +++++++- STARTUP.md | 97 ++++ agent/example.env | 5 + agent/langgraph.json | 2 +- agent/mcp-agent/agent.py | 140 ----- agent/{mcp-agent => mcp_agent}/__init__.py | 0 agent/mcp_agent/agent.py | 247 +++++++++ agent/mcp_agent/agent_factory.py | 125 +++++ agent/mcp_agent/custom_server.py | 127 +++++ agent/mcp_agent/custom_server_entry.py | 98 ++++ agent/mcp_agent/health_server.py | 115 ++++ agent/mcp_agent/integrations/__init__.py | 1 + .../mcp_agent/integrations/a2a_integration.py | 44 ++ agent/mcp_agent/integrations/a2a_protocols.py | 41 ++ agent/mcp_agent/integrations/base_memory.py | 23 + .../integrations/graphiti_integration.py | 162 ++++++ .../integrations/litellm_integration.py | 82 +++ .../integrations/logfire_integration.py | 72 ++- .../integrations/mem0_integration.py | 207 +++++++ .../integrations/memorysaver_manager.py | 91 +++ agent/mcp_agent/langgraph.json | 14 +- agent/mcp_agent/run_server.py | 53 +- agent/mcp_agent/test_agent_factory.py | 53 ++ agent/poetry.lock | 518 ++++++++++++------ agent/pyproject.toml | 17 +- agent/test_server.py | 96 ++++ check-health-fixed.ps1 | 175 ++++++ check-health.bat | 96 ++++ check-health.ps1 | 175 ++++++ frontend/README.md | 94 +++- frontend/eslint.config.mjs | 111 ++++ frontend/example.env | 1 + frontend/package.json | 7 +- frontend/pnpm-lock.yaml | 14 + frontend/src/app/(canvas-pages)/layout.tsx | 8 +- frontend/src/app/globals.css | 321 +++++++++-- frontend/src/app/layout.tsx | 11 +- frontend/src/app/settings/layout.tsx | 8 +- frontend/src/app/settings/page.tsx | 43 +- frontend/src/components/agents/index.tsx | 1 + .../src/components/agents/knowledge-agent.tsx | 97 +++- frontend/src/components/agents/mcp-agent.tsx | 179 ++++-- frontend/src/components/agents/researcher.tsx | 125 +++-- frontend/src/components/agents/travel.tsx | 4 +- frontend/src/components/app-sidebar.tsx | 7 +- frontend/src/components/canvas.tsx | 140 +++-- frontend/src/components/chat-window.tsx | 65 ++- frontend/src/components/coagents-provider.tsx | 53 +- frontend/src/components/enhanced-layout.tsx | 16 +- frontend/src/components/entity-form.tsx | 61 ++- frontend/src/components/map-container.tsx | 97 ++-- frontend/src/components/map.tsx | 1 + frontend/src/components/mcp-config-modal.tsx | 233 ++++---- frontend/src/components/research-logs.tsx | 47 +- frontend/src/components/settings-modal.tsx | 136 +++-- frontend/src/components/skeletons/index.tsx | 172 +++--- frontend/src/components/ui/button.tsx | 1 + frontend/src/components/ui/checkbox.tsx | 1 + frontend/src/components/ui/dialog.tsx | 17 +- .../src/components/ui/loading-animation.tsx | 1 + frontend/src/components/ui/separator.tsx | 1 + frontend/src/components/ui/sheet.tsx | 17 +- frontend/src/components/ui/sidebar.tsx | 8 +- frontend/src/components/ui/tooltip.tsx | 1 + frontend/src/hooks/use-local-storage.tsx | 2 +- frontend/src/lib/available-agents.ts | 1 + frontend/src/providers/Providers.tsx | 35 +- frontend/src/providers/SettingsProvider.tsx | 4 +- simple-check.ps1 | 39 ++ start-all.bat | 62 +++ start-all.ps1 | 203 +++++++ stop-all.bat | 35 ++ stop-all.ps1 | 87 +++ 81 files changed, 4823 insertions(+), 1012 deletions(-) rename {frontend => .cursor}/.cursorrules (100%) create mode 100644 .cursor/rules/agent-best-practices.mdc create mode 100644 .cursor/rules/context7-mcp-tool.mdc create mode 100644 .cursor/rules/frontend-best-practices.mdc create mode 100644 .cursor/rules/fullstack-best-practices.mdc create mode 100644 STARTUP.md delete mode 100644 agent/mcp-agent/agent.py rename agent/{mcp-agent => mcp_agent}/__init__.py (100%) create mode 100644 agent/mcp_agent/agent.py create mode 100644 agent/mcp_agent/agent_factory.py create mode 100644 agent/mcp_agent/custom_server.py create mode 100644 agent/mcp_agent/custom_server_entry.py create mode 100644 agent/mcp_agent/health_server.py create mode 100644 agent/mcp_agent/integrations/__init__.py create mode 100644 agent/mcp_agent/integrations/a2a_integration.py create mode 100644 agent/mcp_agent/integrations/a2a_protocols.py create mode 100644 agent/mcp_agent/integrations/base_memory.py create mode 100644 agent/mcp_agent/integrations/graphiti_integration.py create mode 100644 agent/mcp_agent/integrations/litellm_integration.py create mode 100644 agent/mcp_agent/integrations/mem0_integration.py create mode 100644 agent/mcp_agent/integrations/memorysaver_manager.py create mode 100644 agent/mcp_agent/test_agent_factory.py create mode 100644 agent/test_server.py create mode 100644 check-health-fixed.ps1 create mode 100644 check-health.bat create mode 100644 check-health.ps1 create mode 100644 simple-check.ps1 create mode 100644 start-all.bat create mode 100644 start-all.ps1 create mode 100644 stop-all.bat create mode 100644 stop-all.ps1 diff --git a/frontend/.cursorrules b/.cursor/.cursorrules similarity index 100% rename from frontend/.cursorrules rename to .cursor/.cursorrules diff --git a/.cursor/rules/agent-best-practices.mdc b/.cursor/rules/agent-best-practices.mdc new file mode 100644 index 0000000..17ddf13 --- /dev/null +++ b/.cursor/rules/agent-best-practices.mdc @@ -0,0 +1,39 @@ +--- +description: Backend +globs: +alwaysApply: false +--- +# Agent Backend Best Practices Rule + +To ensure easy, non-breaking development in the agent backend (`/agent`), follow these guidelines: + +## Project Structure +- The main agent logic is in .@`mcp_agent\agents.py` +- Add new agent logic or workflows as new nodes in the workflow graph. +- Place supporting scripts (e.g., math server) in the agent directory (e.g., [`math_server.py`](mdc:agent/math_server.py)). + +## Extensibility +- Use the `MCPConfig` pattern to add new tools or servers without modifying core logic. +- Extend `AgentState` for new agent/team features, but keep backward compatibility. +- Add new workflow nodes for advanced agent/team orchestration, not by modifying existing nodes. +- For advanced memory, swap `MemorySaver` for a compatible memory backend (e.g., Mem0) in a non-breaking way. + +## Code Quality +- Use type hints and TypedDict for all state/config structures. +- Write docstrings for all functions and classes. +- Use camelCase for variables and PascalCase for classes. +- Prefer async/await for all agent logic. +- Log tool and agent responses for debugging. + +## Backward Compatibility +- Default to existing config and memory if new features/settings are not provided. +- Never remove or rename existing config fields without a migration path. +- Test all changes with existing agent workflows before merging. + +## Documentation +- Update [`README.md`](mdc:agent/README.md) with any major changes or new features. +- Document new tools, memory backends, or workflow nodes inline and in the README. + +## Summary +Following these rules will help maintain a robust, extensible, and backward-compatible agent backend. + diff --git a/.cursor/rules/context7-mcp-tool.mdc b/.cursor/rules/context7-mcp-tool.mdc new file mode 100644 index 0000000..16b529b --- /dev/null +++ b/.cursor/rules/context7-mcp-tool.mdc @@ -0,0 +1,26 @@ +--- +description: use context7 +globs: +alwaysApply: false +--- +# Context7 MCP Tool Usage Rule + +When using the context7 MCP tool, especially the `get-library-docs` action, always set the `tokens` parameter to **5000 or greater**. This is required to avoid errors and ensure successful documentation retrieval. + +## Example (Correct Usage) + +```json +{ + "context7CompatibleLibraryID": "/mem0ai/mem0", + "tokens": 5000 +} +``` + +## Best Practices +- Never set `tokens` below 5000 for `get-library-docs`. +- If specifying a topic, include it as a string, but always set `tokens` to at least 5000. +- Update any tool call templates or scripts to default to `tokens: 5000` or higher. + +## Reference +This rule is enforced to comply with the requirements of the context7 MCP tool and prevent repeated errors in documentation retrieval. + diff --git a/.cursor/rules/frontend-best-practices.mdc b/.cursor/rules/frontend-best-practices.mdc new file mode 100644 index 0000000..cca505b --- /dev/null +++ b/.cursor/rules/frontend-best-practices.mdc @@ -0,0 +1,46 @@ +--- +description: frontend, ui. +globs: +alwaysApply: false +--- +# Frontend Best Practices Rule + +To ensure robust, bug-free development in the frontend (`/frontend`), follow these guidelines: + +## Project Structure +- Main app entry and routing are in [`src/app/`](mdc:frontend/src/app). +- Reusable UI components are in [`src/components/ui/`](mdc:frontend/src/components/ui). +- Agent-specific UIs are in [`src/components/agents/`](mdc:frontend/src/components/agents). +- Global state providers are in [`src/providers/`](mdc:frontend/src/providers). +- Custom hooks are in [`src/hooks/`](mdc:frontend/src/hooks). +- Utility functions and agent config types are in [`src/lib/`](mdc:frontend/src/lib). + +## Code Quality +- Use TypeScript for all files to ensure type safety. +- Write JSDoc comments for all functions and components. +- Use camelCase for variables and PascalCase for components/classes. +- Prefer async/await for asynchronous logic. +- Use React context for global state management. +- Keep components small and focused; extract logic into hooks when possible. + +## UI/UX +- Use Tailwind CSS for styling; avoid inline styles. +- Ensure all UI is responsive and mobile-friendly. +- Use skeletons/loading states from [`src/components/skeletons/`](mdc:frontend/src/components/skeletons) for async data. + +## Extensibility +- Register new agents in [`src/lib/available-agents.ts`](mdc:frontend/src/lib/available-agents.ts). +- Add new agent UIs in [`src/components/agents/`](mdc:frontend/src/components/agents). +- Use the agent/team creation UI (planned) for dynamic agent management. + +## Testing & Debugging +- Test all new features in both development and production builds. +- Use React Query Devtools and CopilotKit Dev Console for debugging. +- Validate all forms and user input. + +## Documentation +- Update [`README.md`](mdc:frontend/README.md) with any major changes or new features. + +## Summary +Following these rules will help maintain a clean, scalable, and bug-resistant frontend codebase. + diff --git a/.cursor/rules/fullstack-best-practices.mdc b/.cursor/rules/fullstack-best-practices.mdc new file mode 100644 index 0000000..45a8eb2 --- /dev/null +++ b/.cursor/rules/fullstack-best-practices.mdc @@ -0,0 +1,43 @@ +--- +description: +globs: +alwaysApply: true +--- +# Fullstack Best Practices Rule + +To ensure a complete, bug-free app across both frontend and backend, follow these unified guidelines: + +## Project Structure +- Frontend code is in [`/frontend`](mdc:frontend/README.md), with main logic in [`src/app/`](mdc:frontend/src/app) and components in [`src/components/`](mdc:frontend/src/components). +- Backend agent code is in [`/agent`](mdc:agent/README.md), with main logic in .@`mcp_agent\agents.py`. + +## Code Quality +- Use TypeScript for frontend and type hints for backend Python. +- Write JSDoc (frontend) and docstrings (backend) for all functions and classes. +- Use camelCase for variables and PascalCase for components/classes. +- Prefer async/await for all asynchronous logic. +- Keep components, functions, and modules small and focused. + +## Extensibility +- Register new agents and tools via config files ([`src/lib/available-agents.ts`](mdc:frontend/src/lib/available-agents.ts) for frontend, `MCPConfig` for backend). +- Add new features as new components, hooks, or workflow nodes—avoid modifying existing logic unless necessary. +- Use context providers (frontend) and state classes (backend) for global/shared state. + +## Testing & Debugging +- Test all new features in both development and production builds. +- Use React Query Devtools and CopilotKit Dev Console (frontend) and log tool/agent responses (backend). +- Validate all forms and user input. +- Add unit/integration tests for critical workflows. + +## Documentation +- Update [`README.md`](mdc:frontend/README.md) and [`README.md`](mdc:agent/README.md) with any major changes or new features. +- Document new agents, tools, memory backends, or workflow nodes inline and in the README. + +## Backward Compatibility +- Default to existing config and memory if new features/settings are not provided. +- Never remove or rename existing config fields without a migration path. +- Test all changes with existing workflows before merging. + +## Summary +Following these rules will help maintain a robust, extensible, and bug-resistant fullstack codebase. + diff --git a/.idea/AugmentWebviewStateStore.xml b/.idea/AugmentWebviewStateStore.xml index cab5dbd..f342de4 100644 --- a/.idea/AugmentWebviewStateStore.xml +++ b/.idea/AugmentWebviewStateStore.xml @@ -3,7 +3,7 @@ diff --git a/.idea/KB-multi-agent.iml b/.idea/KB-multi-agent.iml index d6ebd48..794a83f 100644 --- a/.idea/KB-multi-agent.iml +++ b/.idea/KB-multi-agent.iml @@ -1,9 +1,17 @@ + + + + + - - + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 639900d..f154ed9 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,9 @@ - + + + \ No newline at end of file diff --git a/README.md b/README.md index 9cdec16..5931c76 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@
# Open Multi-Agent Canvas - + ![CopilotKit-Banner](https://github.com/user-attachments/assets/8167c845-0381-45d9-ad1c-83f995d48290)
@@ -19,29 +19,99 @@ Check out these awesome agents (they live in separate repositories). You can run - [CoAgents Travel Agent](https://github.com/CopilotKit/CopilotKit/tree/main/examples/coagents-travel/agent) - [CoAgents AI Researcher](https://github.com/CopilotKit/CopilotKit/tree/main/examples/coagents-ai-researcher/agent) -Additionally, this project now includes a built-in MCP (Multi-Channel Protocol) Agent: +Additionally, this project now includes built-in agents: - **MCP Agent**: A general-purpose agent capable of handling various tasks through configurable MCP servers. +- **Knowledge Agent**: A specialized agent for visualizing, querying, and managing knowledge graphs. + +## Copilot Cloud is required to run this project + +You'll need a Copilot Cloud API key to run the frontend. Get one at [cloud.copilotkit.ai](https://cloud.copilotkit.ai/). + +## Architecture Overview + +The Multi-Agent Canvas consists of two main components: + +1. **Frontend (Next.js)**: + - Provides the user interface for interacting with agents + - Manages agent state using CopilotKit + - Communicates with the backend via Server-Sent Events (SSE) + +2. **Backend (LangGraph)**: + - Runs the agent workflows using LangGraph + - Provides API endpoints for the frontend + - Integrates with various tools and services + - Includes a custom server with health and diagnostic endpoints + +### Server Implementation + +The backend includes two server implementations: -## Copilot Cloud is required to run this project: +1. **Custom Server** (Recommended): + - Enhanced FastAPI server with health endpoint + - Improved error handling and diagnostics + - Automatic fallback to simple health server +2. **Standard LangGraph Server**: + - Default LangGraph development server + - Provides graph visualization and debugging tools +The start-all.ps1 script attempts to start the custom server first, and if that fails, it falls back to the standard server. ## Quick Start 🚀 ### 1. Prerequisites Make sure you have: -- [pnpm](https://pnpm.io/installation) +- [pnpm](https://pnpm.io/installation) for frontend dependencies +- [Poetry](https://python-poetry.org/docs/#installation) for backend dependencies +- [Python](https://www.python.org/downloads/) (v3.10 or later) +- [Node.js](https://nodejs.org/) (v18 or later) ### 2. API Keys -- [Copilot Cloud](https://cloud.copilotkit.ai) +- [Copilot Cloud](https://cloud.copilotkit.ai) for the frontend +- OpenAI API key for the backend (optional) -## Running the Frontend +### 3. One-Click Startup (Recommended) + +We've created convenient scripts to start both the frontend and backend with a single command: + +**Windows (PowerShell - Recommended):** +``` +./start-all.ps1 +``` + +**Windows (Batch):** +``` +start-all.bat +``` + +These scripts will: +- Check for required dependencies +- Start the backend with a health endpoint +- Start the frontend +- Verify the integration between frontend and backend + +To stop all services: +``` +./stop-all.ps1 # or stop-all.bat +``` + +To check the health of all services: +``` +./check-health.ps1 # or check-health.bat +``` + +For more detailed instructions, see [STARTUP.md](./STARTUP.md). + +### 4. Manual Setup + +#### Running the Frontend Rename the `example.env` file in the `frontend` folder to `.env`: ```sh -NEXT_PUBLIC_CPK_PUBLIC_API_KEY=... +NEXT_PUBLIC_COPILOT_CLOUD_API_KEY=... +NEXT_PUBLIC_BACKEND_URL=http://localhost:8124 ``` Install dependencies: @@ -51,17 +121,19 @@ cd frontend pnpm i ``` - - Need a CopilotKit API key? Get one [here](https://cloud.copilotkit.ai/). Then, fire up the Next.js project: ```sh -pnpm run build && pnpm run start +pnpm run dev # for development +# or +pnpm run build && pnpm run start # for production ``` -## MCP Agent Setup +## Agent Features + +### MCP Agent ![mcp-demo](./agent/demo/mcp-demo.gif) @@ -76,32 +148,147 @@ The MCP Agent allows you to connect to various MCP-compatible servers: 2. **Public MCP Servers**: - You can connect to public MCP servers like [mcp.composio.dev](https://mcp.composio.dev/) and [mcp.run](https://www.mcp.run/) -## Running the MCP Agent Backend (Optional) +### Knowledge Agent + +The Knowledge Agent provides a powerful interface for working with knowledge graphs: + +1. **Interactive Graph Visualization**: + - Visualize entities and relationships in an interactive force-directed graph + - Zoom, pan, and explore the knowledge structure visually + - Filter by entity types and relationship types + +2. **Entity Management**: + - Add new entities to the knowledge graph + - Edit existing entities and their properties + - View detailed information about entities + +3. **Knowledge Querying**: + - Search for entities and concepts in the knowledge graph + - Find relationships between entities + - Explore the knowledge structure through natural language queries + +#### Running the MCP Agent Backend Rename the `example.env` file in the `agent` folder to `.env`: ```sh OPENAI_API_KEY=... LANGSMITH_API_KEY=... + +# Optional Logfire configuration +LOGFIRE_PROJECT=kb-multi-agent +LOGFIRE_TOKEN=... +LOGGING_ENABLED=true ``` -If you want to use the included MCP Agent with the built-in math server: +If you want to use the included MCP Agent with the built-in math and knowledge servers: ```sh cd agent poetry install -poetry run langgraph dev --host localhost --port 8123 --no-browser + +# Standard server (no health endpoint) +poetry run demo + +# OR use the improved server with health endpoint (recommended) +poetry run custom-server ``` +The custom server provides a health endpoint at http://localhost:8124/health that the frontend can use to verify the backend is running. It also includes additional diagnostic endpoints like `/routes` and a root endpoint with server information. + +### Enhanced Server Features + +1. **Robust Error Handling**: The custom server includes comprehensive error handling and fallback mechanisms to ensure reliability. +2. **Diagnostic Endpoints**: + - `/health`: Check if the server is running + - `/routes`: List all available API routes + - `/`: Root endpoint with server information +3. **Automatic Fallback**: If the custom server fails to start, it automatically falls back to a simple health server to maintain basic functionality. + ## Running a tunnel Add another terminal and select Remote Endpoint. Then select Local Development. -Once this is done, copy the command into your terminal and change the port to match the LangGraph server `8123` +Once this is done, copy the command into your terminal and change the port to match the LangGraph server `8124` ![image](https://github.com/user-attachments/assets/6bf41042-9529-4470-8baf-dd076aad31a1) -## Documentation +## Testing the Backend + +A test script is included to verify that the backend is working correctly: + +```sh +cd agent +python test_server.py +``` + +This script tests the health endpoint, routes endpoint, and root endpoint to ensure the server is functioning properly. + +## Backend Integrations + +The backend includes several modular integrations that can be configured via environment variables: + +1. **Memory Management**: + - `mem0`: Integration with [mem0](https://mem0.ai/) for persistent memory storage + - `memorysaver`: Default in-memory storage using LangGraph's MemorySaver + +2. **LLM Integration**: + - `litellm`: Unified interface for various LLM providers through [LiteLLM](https://github.com/BerriAI/litellm) + +3. **Agent-to-Agent Communication**: + - `inmemory`: In-process communication between agents + +4. **Knowledge Graph**: + - `graphiti`: Integration with knowledge graph services + +5. **Logging and Tracing**: + - `logfire`: Integration with [Logfire](https://logfire.ai/) for logging, tracing, and monitoring + - See [Logfire Integration](./agent/LOGFIRE.md) for detailed setup instructions + +## Troubleshooting + +### Port Conflicts + +If you encounter port conflicts when starting the backend server, you may see an error like: + +``` +ERROR: [Errno 10048] error while attempting to bind on address ('127.0.0.1', 8124): [winerror 10048] only one usage of each socket address is allowed +``` + +To resolve this: + +1. Find the process using the port: + ``` + netstat -ano | findstr :8124 + ``` + +2. Terminate the process: + ``` + taskkill /F /PID + ``` + +3. Restart the server + +### Backend Connection Issues + +If the frontend cannot connect to the backend: + +1. Verify the backend is running: + ``` + curl http://localhost:8124/health + ``` + +2. Check that the frontend's `.env` file has the correct backend URL: + ``` + NEXT_PUBLIC_BACKEND_URL=http://localhost:8124 + ``` + +3. Run the test script to verify the backend is working correctly: + ``` + cd agent && python test_server.py + ``` + +## Documentation - [CopilotKit Docs](https://docs.copilotkit.ai/coagents) - [LangGraph Platform Docs](https://langchain-ai.github.io/langgraph/cloud/deployment/cloud/) - [Model Context Protocol (MCP) Docs](https://github.com/langchain-ai/langgraph/tree/main/examples/mcp) diff --git a/STARTUP.md b/STARTUP.md new file mode 100644 index 0000000..dd3bbfe --- /dev/null +++ b/STARTUP.md @@ -0,0 +1,97 @@ +# Multi-Agent Canvas Startup Guide + +This document provides instructions for starting and stopping the Multi-Agent Canvas application, which consists of a frontend (Next.js) and a backend (MCP Agent). + +## Prerequisites + +Before starting the application, ensure you have the following installed: + +- [Poetry](https://python-poetry.org/docs/#installation) for Python dependency management +- [PNPM](https://pnpm.io/installation) for Node.js dependency management +- [Node.js](https://nodejs.org/) (v18 or later) +- [Python](https://www.python.org/downloads/) (v3.10 or later) + +## Starting the Application + +You have two options for starting the application: + +### Option 1: Using the Batch Script (Windows) + +1. Double-click the `start-all.bat` file +2. Two command windows will open - one for the backend and one for the frontend +3. Wait for both services to initialize + +### Option 2: Using the PowerShell Script (Recommended) + +1. Right-click on `start-all.ps1` and select "Run with PowerShell" +2. The script will: + - Check if all prerequisites are installed + - Start the backend and verify it's running + - Start the frontend and verify it's running + - Verify the integration between frontend and backend + - Display the URLs for accessing the application + +## Accessing the Application + +Once both services are running, you can access: + +- Frontend: [http://localhost:3000](http://localhost:3000) +- Backend API: [http://localhost:8123](http://localhost:8123) +- Backend Health: [http://localhost:8123/health](http://localhost:8123/health) +- API Documentation: [http://localhost:8123/docs](http://localhost:8123/docs) + +## Stopping the Application + +You have two options for stopping the application: + +### Option 1: Using the Batch Script (Windows) + +1. Double-click the `stop-all.bat` file +2. The script will terminate all related processes + +### Option 2: Using the PowerShell Script (Recommended) + +1. Right-click on `stop-all.ps1` and select "Run with PowerShell" +2. The script will: + - Identify and stop all frontend processes + - Identify and stop all backend processes + - Close any remaining terminal windows + +## Troubleshooting + +If you encounter issues: + +1. **Backend fails to start**: + - Check if port 8123 is already in use + - Verify that Poetry is installed correctly + - Check the backend terminal for specific error messages + +2. **Frontend fails to start**: + - Check if port 3000 is already in use + - Verify that PNPM is installed correctly + - Check the frontend terminal for specific error messages + +3. **Connection issues**: + - Verify that both services are running + - Check that the frontend's `.env` file contains `NEXT_PUBLIC_BACKEND_URL=http://localhost:8123` + - Try accessing the backend health endpoint directly: [http://localhost:8123/health](http://localhost:8123/health) + +## Manual Startup (If Scripts Fail) + +If the scripts don't work for any reason, you can start the services manually: + +### Backend (MCP Agent) + +```bash +cd agent +poetry install +poetry run custom-server +``` + +### Frontend (Next.js) + +```bash +cd frontend +pnpm install +pnpm run dev +``` diff --git a/agent/example.env b/agent/example.env index 76081f7..42b7dbb 100644 --- a/agent/example.env +++ b/agent/example.env @@ -1,2 +1,7 @@ OPENAI_API_KEY= LANGSMITH_API_KEY= + +# Logfire Configuration +LOGFIRE_PROJECT=kb-multi-agent +LOGFIRE_TOKEN= +LOGGING_ENABLED=true diff --git a/agent/langgraph.json b/agent/langgraph.json index 6fbfd9a..0769857 100644 --- a/agent/langgraph.json +++ b/agent/langgraph.json @@ -3,7 +3,7 @@ "dockerfile_lines": [], "dependencies": ["."], "graphs": { - "mcp-agent": "./mcp-agent/agent.py:graph" + "mcp-agent": "./mcp_agent/agent.py:graph" }, "env": ".env" } diff --git a/agent/mcp-agent/agent.py b/agent/mcp-agent/agent.py deleted file mode 100644 index 6b4e71b..0000000 --- a/agent/mcp-agent/agent.py +++ /dev/null @@ -1,140 +0,0 @@ -""" -This is the main entry point for the agent. -It defines the workflow graph, state, tools, nodes and edges. -""" - -from typing_extensions import Literal, TypedDict, Dict, List, Any, Union, Optional -from langchain_openai import ChatOpenAI -from langchain_core.runnables import RunnableConfig -from langgraph.graph import StateGraph, END -from langgraph.checkpoint.memory import MemorySaver -from langgraph.types import Command -from copilotkit import CopilotKitState -from langchain_mcp_adapters.client import MultiServerMCPClient -from langgraph.prebuilt import create_react_agent -from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder -import os - -# Define the connection type structures -class StdioConnection(TypedDict): - command: str - args: List[str] - transport: Literal["stdio"] - -class SSEConnection(TypedDict): - url: str - transport: Literal["sse"] - -# Type for MCP configuration -MCPConfig = Dict[str, Union[StdioConnection, SSEConnection]] - -class AgentState(CopilotKitState): - """ - Here we define the state of the agent - - In this instance, we're inheriting from CopilotKitState, which will bring in - the CopilotKitState fields. We're also adding a custom field, `mcp_config`, - which will be used to configure MCP services for the agent. - """ - # Define mcp_config as an optional field without skipping validation - mcp_config: Optional[MCPConfig] - -# Default MCP configuration to use when no configuration is provided in the state -# Uses relative paths that will work within the project structure -DEFAULT_MCP_CONFIG: MCPConfig = { - "math": { - "command": "python", - # Use a relative path that will be resolved based on the current working directory - "args": [os.path.join(os.path.dirname(__file__), "..", "math_server.py")], - "transport": "stdio", - }, -} - -# Define a custom ReAct prompt that encourages the use of multiple tools -MULTI_TOOL_REACT_PROMPT = ChatPromptTemplate.from_messages( - [ - ( - "system", - """You are an assistant that can use multiple tools to solve problems. -You should use a step-by-step approach, using as many tools as needed to find the complete answer. -Don't hesitate to call different tools sequentially if that helps reach a better solution. - -You have access to the following tools: - -{{tools}} - -To use a tool, please use the following format: -``` -Thought: I need to use a tool to help with this. -Action: tool_name -Action Input: the input to the tool -``` - -The observation will be returned in the following format: -``` -Observation: tool result -``` - -When you have the final answer, respond in the following format: -``` -Thought: I can now provide the final answer. -Final Answer: the final answer to the original input -``` - -Begin! -""" - ), - MessagesPlaceholder(variable_name="messages"), - ] -) - -async def chat_node(state: AgentState, config: RunnableConfig) -> Command[Literal["__end__"]]: - """ - This is an enhanced agent that uses a modified ReAct pattern to allow multiple tool use. - It handles both chat responses and sequential tool execution in one node. - """ - # Get MCP configuration from state, or use the default config if not provided - mcp_config = state.get("mcp_config", DEFAULT_MCP_CONFIG) - - # Set up the MCP client and tools using the configuration from state - async with MultiServerMCPClient(mcp_config) as mcp_client: - # Get the tools - mcp_tools = mcp_client.get_tools() - print(f"mcp_tools: {mcp_tools}") - - # Create a model instance - model = ChatOpenAI(model="gpt-4o") - - # Create the enhanced multi-tool react agent with our custom prompt - react_agent = create_react_agent( - model, - mcp_tools, - prompt=MULTI_TOOL_REACT_PROMPT - ) - - # Prepare messages for the react agent - agent_input = { - "messages": state["messages"] - } - - # Run the react agent subgraph with our input - agent_response = await react_agent.ainvoke(agent_input) - - print(f"agent_response: {agent_response}") - - # Update the state with the new messages - updated_messages = state["messages"] + agent_response.get("messages", []) - - # End the graph with the updated messages - return Command( - goto=END, - update={"messages": updated_messages}, - ) - -# Define the workflow graph with only a chat node -workflow = StateGraph(AgentState) -workflow.add_node("chat_node", chat_node) -workflow.set_entry_point("chat_node") - -# Compile the workflow graph -graph = workflow.compile(MemorySaver()) \ No newline at end of file diff --git a/agent/mcp-agent/__init__.py b/agent/mcp_agent/__init__.py similarity index 100% rename from agent/mcp-agent/__init__.py rename to agent/mcp_agent/__init__.py diff --git a/agent/mcp_agent/agent.py b/agent/mcp_agent/agent.py new file mode 100644 index 0000000..26999dd --- /dev/null +++ b/agent/mcp_agent/agent.py @@ -0,0 +1,247 @@ +""" +This is the main entry point for the agent. +It defines the workflow graph, state, tools, nodes and edges. +""" + +import os +import uuid +import logging +from typing_extensions import Literal, TypedDict, Dict, List, Union, Optional +from langchain_openai import ChatOpenAI +from langchain_core.runnables import RunnableConfig +from langgraph.graph import StateGraph, END +from langgraph.checkpoint.memory import MemorySaver +from langgraph.types import Command +from copilotkit import CopilotKitState +from langchain_mcp_adapters.client import MultiServerMCPClient +from langgraph.prebuilt import create_react_agent +from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder +# Import LogfireLogger from the correct location +from mcp_agent.integrations.logfire_integration import LogfireLogger + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Define the connection type structures +class StdioConnection(TypedDict): + command: str + args: List[str] + transport: Literal["stdio"] + +class SSEConnection(TypedDict): + url: str + transport: Literal["sse"] + +# Type for MCP configuration +MCPConfig = Dict[str, Union[StdioConnection, SSEConnection]] + +class AgentState(CopilotKitState): + """ + Here we define the state of the agent + + In this instance, we're inheriting from CopilotKitState, which will bring in + the CopilotKitState fields. We're also adding custom fields for configuration. + """ + # Define mcp_config as an optional field without skipping validation + mcp_config: Optional[MCPConfig] + # Unique conversation ID for tracing + conversation_id: Optional[str] = None + # Trace ID for Logfire + trace_id: Optional[str] = None + +# Default MCP configuration to use when no configuration is provided in the state +# Uses relative paths that will work within the project structure +DEFAULT_MCP_CONFIG: MCPConfig = { + "math": { + "command": "python", + # Use a relative path that will be resolved based on the current working directory + "args": [os.path.join(os.path.dirname(__file__), "..", "math_server.py")], + "transport": "stdio", + }, + "knowledge": { + "command": "python", + "args": [os.path.join(os.path.dirname(__file__), "..", "knowledge_server.py")], + "transport": "stdio", + }, +} + +# Define a custom ReAct prompt that encourages the use of multiple tools +MULTI_TOOL_REACT_PROMPT = ChatPromptTemplate.from_messages( + [ + ( + "system", + """You are an assistant that can use multiple tools to solve problems. +You should use a step-by-step approach, using as many tools as needed to find the complete answer. +Don't hesitate to call different tools sequentially if that helps reach a better solution. + +You have access to the following tools: + +{{tools}} + +To use a tool, please use the following format: +``` +Thought: I need to use a tool to help with this. +Action: tool_name +Action Input: the input to the tool +``` + +The observation will be returned in the following format: +``` +Observation: tool result +``` + +When you have the final answer, respond in the following format: +``` +Thought: I can now provide the final answer. +Final Answer: the final answer to the original input +``` + +Begin! +""" + ), + MessagesPlaceholder(variable_name="messages"), + ] +) + +# Initialize Logfire logger +logfire_logger = LogfireLogger() + +async def chat_node(state: AgentState, config: RunnableConfig) -> Command[Literal["__end__"]]: + """ + This is an enhanced agent that uses a modified ReAct pattern to allow multiple tool use. + It handles both chat responses and sequential tool execution in one node. + """ + # Ensure we have a conversation ID and trace ID for logging + conversation_id = state.get("conversation_id") or str(uuid.uuid4()) + trace_id = state.get("trace_id") or str(uuid.uuid4()) + + # Get MCP configuration from state, or use the default config if not provided + mcp_config = state.get("mcp_config", DEFAULT_MCP_CONFIG) + + # Extract the latest user message for logging + latest_user_message = "" + if state["messages"] and len(state["messages"]) > 0: + for msg in reversed(state["messages"]): + if msg.get("role") == "user": + latest_user_message = msg.get("content", "") + break + + # Log the start of processing with Logfire + logfire_logger.log_event("chat_processing_started", { + "conversation_id": conversation_id, + "trace_id": trace_id, + "user_message": latest_user_message, + "message_count": len(state["messages"]), + }) + + # Set up the MCP client and tools using the configuration from state + async with logfire_logger.span("mcp_client_setup", { + "conversation_id": conversation_id, + "trace_id": trace_id, + }): + async with MultiServerMCPClient(mcp_config) as mcp_client: + # Get the tools + mcp_tools = mcp_client.get_tools() + logger.info(f"MCP tools loaded: {len(mcp_tools)} tools available") + + # Log tool information + tool_names = [tool.name for tool in mcp_tools] + logfire_logger.log_event("tools_loaded", { + "conversation_id": conversation_id, + "trace_id": trace_id, + "tool_count": len(mcp_tools), + "tool_names": tool_names, + }) + + # Create a model instance with Logfire tracing + model_name = "gpt-4o" + with logfire_logger.span("llm_setup", { + "conversation_id": conversation_id, + "trace_id": trace_id, + "model": model_name, + }): + model = ChatOpenAI(model=model_name) + + # Log model information + logfire_logger.log_event("model_initialized", { + "conversation_id": conversation_id, + "trace_id": trace_id, + "model": model_name, + }) + + # Create the enhanced multi-tool react agent with our custom prompt + react_agent = create_react_agent( + model, + mcp_tools, + prompt=MULTI_TOOL_REACT_PROMPT + ) + + # Prepare messages for the react agent + agent_input = { + "messages": state["messages"] + } + + # Run the react agent subgraph with our input and trace with Logfire + async with logfire_logger.span("agent_execution", { + "conversation_id": conversation_id, + "trace_id": trace_id, + "message_count": len(state["messages"]), + }): + try: + agent_response = await react_agent.ainvoke(agent_input) + + # Log successful agent response + logfire_logger.log_event("agent_execution_completed", { + "conversation_id": conversation_id, + "trace_id": trace_id, + "response_message_count": len(agent_response.get("messages", [])), + }) + except Exception as e: + # Log error if agent execution fails + logfire_logger.log_error(e, { + "conversation_id": conversation_id, + "trace_id": trace_id, + "error_location": "agent_execution", + }) + # Re-raise the exception + raise + + logger.info(f"Agent completed processing with {len(agent_response.get('messages', []))} response messages") + + # Update the state with the new messages + updated_messages = state["messages"] + agent_response.get("messages", []) + + # Extract agent's response for logging + agent_response_text = "" + if agent_response.get("messages"): + for msg in agent_response.get("messages", []): + if msg.get("role") == "assistant": + agent_response_text = msg.get("content", "") + break + + # Log completion + logfire_logger.log_event("chat_processing_completed", { + "conversation_id": conversation_id, + "trace_id": trace_id, + "total_message_count": len(updated_messages), + "response_length": len(agent_response_text), + }) + + # End the graph with the updated messages and trace IDs + return Command( + goto=END, + update={ + "messages": updated_messages, + "conversation_id": conversation_id, + "trace_id": trace_id, + }, + ) + +# Define the workflow graph with only a chat node +workflow = StateGraph(AgentState) +workflow.add_node("chat_node", chat_node) +workflow.set_entry_point("chat_node") + +# Compile the workflow graph +graph = workflow.compile(MemorySaver()) \ No newline at end of file diff --git a/agent/mcp_agent/agent_factory.py b/agent/mcp_agent/agent_factory.py new file mode 100644 index 0000000..5c70dd1 --- /dev/null +++ b/agent/mcp_agent/agent_factory.py @@ -0,0 +1,125 @@ +# Dynamic Agent Factory with modular backends +import os +import logging +from typing import Optional, Dict, Any, Union, Literal +from mcp_agent.integrations.mem0_integration import Mem0MemoryManager +from mcp_agent.integrations.memorysaver_manager import MemorySaverManager +from mcp_agent.integrations.litellm_integration import LiteLLMWrapper +from mcp_agent.integrations.a2a_integration import A2ACommunicator +from mcp_agent.integrations.graphiti_integration import GraphitiKnowledgeSource +from mcp_agent.integrations.logfire_integration import LogfireLogger + +from mcp_agent.integrations.base_memory import BaseMemoryManager + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +class AgentFactory: + """ + Factory for creating agents with modular, configurable backends. + Reads config/env to select memory, LLM, A2A, and knowledge backends. + """ + def __init__(self, config: Optional[Dict[str, Any]] = None): + self.config = config or self._load_config_from_env() + self.memory_backend = self.config.get("MEMORY_BACKEND", "memorysaver").lower() + self.llm_backend = self.config.get("LLM_BACKEND", "litellm").lower() + self.a2a_backend = self.config.get("A2A_BACKEND", "inmemory").lower() + self.knowledge_backend = self.config.get("KNOWLEDGE_BACKEND", "graphiti").lower() + self.logging_enabled = self.config.get("LOGGING_ENABLED", "true").lower() == "true" + self.logfire_project = self.config.get("LOGFIRE_PROJECT", "kb-multi-agent") + + # Initialize logger + self.logger = self._initialize_logger() + + def _load_config_from_env(self) -> Dict[str, Any]: + """Load backend config from environment variables.""" + return { + "MEMORY_BACKEND": os.getenv("MEMORY_BACKEND", "memorysaver"), + "LLM_BACKEND": os.getenv("LLM_BACKEND", "litellm"), + "A2A_BACKEND": os.getenv("A2A_BACKEND", "inmemory"), + "KNOWLEDGE_BACKEND": os.getenv("KNOWLEDGE_BACKEND", "graphiti"), + "LOGGING_ENABLED": os.getenv("LOGGING_ENABLED", "true"), + "LOGFIRE_PROJECT": os.getenv("LOGFIRE_PROJECT", "kb-multi-agent"), + } + + def _initialize_logger(self) -> LogfireLogger: + """Initialize the Logfire logger.""" + try: + return LogfireLogger( + project_name=self.logfire_project, + enabled=self.logging_enabled + ) + except Exception as e: + logger.warning(f"Failed to initialize Logfire logger: {e}") + # Return a disabled logger as fallback + return LogfireLogger(enabled=False) + + def get_memory_manager(self) -> BaseMemoryManager: + if self.memory_backend == "mem0": + return Mem0MemoryManager() + elif self.memory_backend == "memorysaver": + return MemorySaverManager() + else: + raise ValueError(f"Unknown memory backend: {self.memory_backend}") + + def get_llm_client(self) -> LiteLLMWrapper: + # Only LiteLLM implemented for now + if self.llm_backend == "litellm": + return LiteLLMWrapper() + else: + raise ValueError(f"Unknown LLM backend: {self.llm_backend}") + + def get_a2a_communicator(self) -> A2ACommunicator: + # Only in-memory implemented for now + if self.a2a_backend == "inmemory": + return A2ACommunicator() + else: + raise ValueError(f"Unknown A2A backend: {self.a2a_backend}") + + def get_knowledge_source(self): + """Get the configured knowledge source based on KNOWLEDGE_BACKEND setting""" + if self.knowledge_backend == "graphiti": + return GraphitiKnowledgeSource() + else: + return None + + def get_logger(self) -> LogfireLogger: + """Get the configured logger""" + return self.logger + + def create_agent(self, agent_class, **kwargs): + """ + Instantiate an agent with the selected backends injected. + agent_class: the class of the agent to instantiate. + kwargs: any additional arguments for the agent. + """ + memory = self.get_memory_manager() + llm = self.get_llm_client() + a2a = self.get_a2a_communicator() + knowledge = self.get_knowledge_source() + logger = self.get_logger() + + # Log agent creation + agent_id = kwargs.get("agent_id", "unknown") + logger.log_event("agent_created", { + "agent_id": agent_id, + "agent_class": agent_class.__name__, + "memory_backend": self.memory_backend, + "llm_backend": self.llm_backend, + "a2a_backend": self.a2a_backend, + "knowledge_backend": self.knowledge_backend + }) + + return agent_class( + memory=memory, + llm=llm, + a2a=a2a, + knowledge=knowledge, + logger=logger, + **kwargs + ) + +# Example usage: +# factory = AgentFactory() +# my_agent = factory.create_agent(MyAgentClass, agent_id="agent-123") \ No newline at end of file diff --git a/agent/mcp_agent/custom_server.py b/agent/mcp_agent/custom_server.py new file mode 100644 index 0000000..7a88a3f --- /dev/null +++ b/agent/mcp_agent/custom_server.py @@ -0,0 +1,127 @@ +""" +Custom server script for the MCP Agent. +This creates a standalone FastAPI server with health endpoint and LangGraph integration. +""" + +import os +import sys +import logging +import importlib +from pathlib import Path +from typing import Dict, Any, Optional +from fastapi import FastAPI, Request, Depends, HTTPException +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse +from pydantic import BaseModel + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Define models for API requests and responses +class GraphRequest(BaseModel): + inputs: Dict[str, Any] + config: Optional[Dict[str, Any]] = None + +class GraphResponse(BaseModel): + outputs: Dict[str, Any] + +# Create a new FastAPI app +app = FastAPI( + title="MCP Agent Server", + description="Custom server with LangGraph integration and health endpoint", + version="0.1.0" +) + + +def create_app(): + """Create a FastAPI app with LangGraph integration and health endpoint.""" + # Get the current directory (mcp_agent) + current_dir = Path(__file__).parent + + # Get the parent directory (agent) + agent_dir = current_dir.parent + + # Change to the agent directory + os.chdir(agent_dir) + + # Log the app creation + logger.info(f"Created FastAPI app for MCP Agent server") + + # Add CORS middleware + app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # In production, you should restrict this + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) + + # Add a health endpoint + @app.get("/health") + async def health_check(): + return {"status": "ok", "message": "MCP Agent backend is running"} + + # Add an endpoint to list all available routes (useful for debugging) + @app.get("/routes") + async def list_routes(): + routes = [] + for route in app.routes: + route_info = { + "path": route.path, + "name": route.name, + "methods": list(route.methods) if hasattr(route, 'methods') else [] + } + routes.append(route_info) + return {"routes": routes} + + # Add a root endpoint + @app.get("/") + async def root(): + return { + "name": "MCP Agent Server", + "version": "0.1.0", + "status": "running", + "endpoints": [ + {"path": "/health", "method": "GET", "description": "Health check endpoint"}, + {"path": "/routes", "method": "GET", "description": "List all available routes"}, + {"path": "/", "method": "GET", "description": "Root endpoint with server information"} + ] + } + + # Add LangGraph integration endpoints + try: + from mcp_agent.agent import graph + + @app.post("/v1/graphs/mcp-agent/invoke") + async def invoke_graph(request: GraphRequest): + try: + # Run the graph with the provided inputs + result = await graph.ainvoke(request.inputs, request.config) + return GraphResponse(outputs=result) + except Exception as e: + logger.error(f"Error invoking graph: {e}") + raise HTTPException(status_code=500, detail=str(e)) + except ImportError as e: + logger.warning(f"Could not import graph from mcp_agent.agent: {e}") + logger.warning("LangGraph integration endpoints will not be available") + + # Add global exception handler to provide better error messages + @app.exception_handler(Exception) + async def global_exception_handler(request: Request, exc: Exception): + logger.error(f"Global exception handler caught: {exc}") + return JSONResponse( + status_code=500, + content={ + "error": str(exc), + "type": str(type(exc).__name__), + "path": request.url.path + } + ) + + # Log all available routes + logger.info(f"Available routes:") + for route in app.routes: + logger.info(f" {route.path} [{','.join(route.methods) if hasattr(route, 'methods') else 'N/A'}]") + + return app diff --git a/agent/mcp_agent/custom_server_entry.py b/agent/mcp_agent/custom_server_entry.py new file mode 100644 index 0000000..78ff6c6 --- /dev/null +++ b/agent/mcp_agent/custom_server_entry.py @@ -0,0 +1,98 @@ +""" +Entry point for the custom server with health endpoint. +This script attempts to start the custom server with LangGraph integration. +If that fails, it falls back to a simple health server. +""" + +import sys +import os +import time +import platform +import traceback +import uvicorn +import logging +from pathlib import Path + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +def run_custom_server(): + """Run the custom server with health endpoint.""" + try: + # Get the current directory (mcp_agent) + current_dir = Path(__file__).parent + + # Get the parent directory (agent) + agent_dir = current_dir.parent + + # Change to the agent directory + os.chdir(agent_dir) + + # Log system information + logger.info(f"Starting MCP Agent server") + logger.info(f"Python version: {sys.version}") + logger.info(f"Platform: {platform.platform()}") + logger.info(f"Working directory: {os.getcwd()}") + + print(f"Starting custom MCP Agent server on http://localhost:8123") + print(f"Health endpoint available at http://localhost:8123/health") + + # Try to run the custom server + try: + logger.info("Attempting to start custom server with LangGraph integration") + + # Check if the custom_server module can be imported + try: + from mcp_agent import custom_server + logger.info("Successfully imported custom_server module") + except ImportError as ie: + logger.error(f"Failed to import custom_server module: {ie}") + raise + + # Start the server with more stable configuration on port 8124 + # Using a different port to avoid conflicts + port = 8124 + print(f"Starting custom MCP Agent server on http://localhost:{port}") + print(f"Health endpoint available at http://localhost:{port}/health") + print(f"LangGraph endpoint available at http://localhost:{port}/v1/graphs/mcp-agent/invoke") + uvicorn.run( + "mcp_agent.custom_server:create_app", + host="localhost", + port=port, + factory=True, + reload=False, # Disable auto-reload to avoid issues + log_level="info", + access_log=True, + timeout_keep_alive=65 # Increase keep-alive timeout + ) + except Exception as e: + logger.error(f"Failed to start custom server: {e}") + logger.error(f"Exception type: {type(e).__name__}") + logger.error(f"Traceback: {traceback.format_exc()}") + logger.info("Falling back to simple health server") + print(f"\nFalling back to simple health server...") + + # Wait a moment to ensure any port conflicts are resolved + time.sleep(1) + + # Run the enhanced health server as fallback + try: + from mcp_agent.health_server import run_server + run_server() + except Exception as health_error: + logger.critical(f"Failed to start fallback health server: {health_error}") + logger.critical(f"Traceback: {traceback.format_exc()}") + print(f"\nCritical error: Failed to start any server") + sys.exit(1) + + except KeyboardInterrupt: + print("\nServer stopped by user.") + except Exception as e: + logger.critical(f"Unhandled exception: {e}") + logger.critical(f"Traceback: {traceback.format_exc()}") + print(f"\nError starting any server: {e}") + sys.exit(1) + +if __name__ == "__main__": + run_custom_server() diff --git a/agent/mcp_agent/health_server.py b/agent/mcp_agent/health_server.py new file mode 100644 index 0000000..3bfc5dd --- /dev/null +++ b/agent/mcp_agent/health_server.py @@ -0,0 +1,115 @@ +""" +Enhanced health server for the MCP Agent. +This provides a minimal server with health and diagnostic endpoints. +This server is used as a fallback when the custom server fails to start. +""" + +import os +import sys +import platform +import logging +import uvicorn +from fastapi import FastAPI, Request +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Create a new FastAPI app +app = FastAPI( + title="MCP Agent Health Server", + description="A fallback server with health and diagnostic endpoints", + version="0.1.0" +) + +# Add CORS middleware +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # In production, you should restrict this + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +@app.get("/health") +async def health_check(): + """Health check endpoint.""" + return {"status": "ok", "message": "MCP Agent backend is running (fallback health server)"} + +@app.get("/") +async def root(): + """Root endpoint with server information.""" + return { + "name": "MCP Agent Health Server", + "version": "0.1.0", + "status": "running", + "mode": "fallback", + "endpoints": [ + {"path": "/health", "method": "GET", "description": "Health check endpoint"}, + {"path": "/", "method": "GET", "description": "Root endpoint with server information"}, + {"path": "/system", "method": "GET", "description": "System information endpoint"}, + {"path": "/routes", "method": "GET", "description": "List all available routes"} + ] + } + +@app.get("/system") +async def system_info(): + """System information endpoint.""" + return { + "python_version": sys.version, + "platform": platform.platform(), + "system": platform.system(), + "processor": platform.processor(), + "cwd": os.getcwd(), + "env_vars": {k: v for k, v in os.environ.items() if k.startswith(('LANGGRAPH_', 'OPENAI_', 'ANTHROPIC_'))} + } + +@app.get("/routes") +async def list_routes(): + """List all available routes.""" + routes = [] + for route in app.routes: + route_info = { + "path": route.path, + "name": route.name, + "methods": list(route.methods) if hasattr(route, 'methods') else [] + } + routes.append(route_info) + return {"routes": routes} + +# Add global exception handler +@app.exception_handler(Exception) +async def global_exception_handler(request: Request, exc: Exception): + logger.error(f"Global exception handler caught: {exc}") + return JSONResponse( + status_code=500, + content={ + "error": str(exc), + "type": str(type(exc).__name__), + "path": request.url.path + } + ) + +def run_server(port=8124): + """Run the health server.""" + logger.info(f"Starting fallback health server on http://localhost:{port}") + logger.info("This is a minimal server with only basic endpoints") + logger.info("The custom server with LangGraph integration could not be started") + try: + # Use more stable configuration + uvicorn.run( + app, + host="localhost", + port=port, + log_level="info", + access_log=True, + timeout_keep_alive=65 # Increase keep-alive timeout + ) + except Exception as e: + logger.error(f"Error running fallback health server: {e}") + sys.exit(1) + +if __name__ == "__main__": + run_server() diff --git a/agent/mcp_agent/integrations/__init__.py b/agent/mcp_agent/integrations/__init__.py new file mode 100644 index 0000000..8c9b930 --- /dev/null +++ b/agent/mcp_agent/integrations/__init__.py @@ -0,0 +1 @@ +# Init file to make 'integrations' a Python package \ No newline at end of file diff --git a/agent/mcp_agent/integrations/a2a_integration.py b/agent/mcp_agent/integrations/a2a_integration.py new file mode 100644 index 0000000..4cc73bd --- /dev/null +++ b/agent/mcp_agent/integrations/a2a_integration.py @@ -0,0 +1,44 @@ +from typing import Dict, Optional, Callable, Awaitable +from .a2a_protocols import A2AMessage +import asyncio + +class A2ACommunicator: + """ + In-memory, async, extensible communicator for agent-to-agent messaging. + Can be extended to use HTTP, message brokers, etc. + """ + def __init__(self): + # In-memory message queues per agent + self.queues: Dict[str, asyncio.Queue] = {} + # Optional: callbacks for message receipt + self.callbacks: Dict[str, Callable[[A2AMessage], Awaitable[None]]] = {} + + async def send_message(self, message: A2AMessage) -> None: + """ + Send a message to another agent (by recipient_agent_id). + """ + queue = self.queues.setdefault(message.recipient_agent_id, asyncio.Queue()) + await queue.put(message) + # If a callback is registered, call it + if message.recipient_agent_id in self.callbacks: + await self.callbacks[message.recipient_agent_id](message) + + async def receive_message(self, agent_id: str, timeout: Optional[float] = None) -> Optional[A2AMessage]: + """ + Receive the next message for this agent (by agent_id). + If timeout is set, waits up to timeout seconds. + """ + queue = self.queues.setdefault(agent_id, asyncio.Queue()) + try: + message = await asyncio.wait_for(queue.get(), timeout=timeout) + return message + except asyncio.TimeoutError: + return None + + def register_callback(self, agent_id: str, callback: Callable[[A2AMessage], Awaitable[None]]): + """ + Register a coroutine callback to be called when a message is sent to this agent. + """ + self.callbacks[agent_id] = callback + + # Extension point: add HTTP, broker, or distributed transport here \ No newline at end of file diff --git a/agent/mcp_agent/integrations/a2a_protocols.py b/agent/mcp_agent/integrations/a2a_protocols.py new file mode 100644 index 0000000..485423c --- /dev/null +++ b/agent/mcp_agent/integrations/a2a_protocols.py @@ -0,0 +1,41 @@ +from typing import Optional, Dict, Any, List, Union +from pydantic import BaseModel, Field +from datetime import datetime + +class TaskRequestPayload(BaseModel): + """ + Example payload for requesting a task from another agent. + Extend as needed for your domain. + """ + task_type: str + parameters: Dict[str, Any] = Field(default_factory=dict) + context: Optional[Dict[str, Any]] = None + +class TaskResponsePayload(BaseModel): + """ + Example payload for responding to a task request. + """ + success: bool + result: Optional[Any] = None + error: Optional[str] = None + +class A2AMessage(BaseModel): + """ + Standardized message for agent-to-agent communication. + """ + sender_agent_id: str + recipient_agent_id: str + message_type: str # e.g., 'task_request', 'task_response', 'status', etc. + payload: Union[TaskRequestPayload, TaskResponsePayload, Dict[str, Any]] + timestamp: datetime = Field(default_factory=datetime.utcnow) + metadata: Optional[Dict[str, Any]] = None + +class CapabilityDiscoveryPayload(BaseModel): + """ + Payload for capability discovery between agents. + """ + capabilities: List[str] + agent_version: Optional[str] = None + extra: Optional[Dict[str, Any]] = None + +# You can extend with more message types as needed (negotiation, state sync, etc.) \ No newline at end of file diff --git a/agent/mcp_agent/integrations/base_memory.py b/agent/mcp_agent/integrations/base_memory.py new file mode 100644 index 0000000..af987be --- /dev/null +++ b/agent/mcp_agent/integrations/base_memory.py @@ -0,0 +1,23 @@ +from typing import Protocol, Any, List, Dict, Optional + +class BaseMemoryManager(Protocol): + """ + Protocol for semantic memory backends. All memory managers must implement these methods. + """ + async def add_memory(self, user_id: str, content: Optional[str] = None, messages: Optional[List[Dict[str, str]]] = None, metadata: Optional[dict] = None) -> Any: + ... + + async def search_memory(self, user_id: str, query: str, limit: int = 5, filters: Optional[Dict] = None) -> List[Dict[str, Any]]: + ... + + async def get_all_memories(self, user_id: str) -> List[Dict[str, Any]]: + ... + + async def get_memory(self, memory_id: str) -> Optional[Dict[str, Any]]: + ... + + async def update_memory(self, memory_id: str, data: Dict[str, Any]) -> Any: + ... + + async def delete_memory(self, memory_id: str) -> Any: + ... \ No newline at end of file diff --git a/agent/mcp_agent/integrations/graphiti_integration.py b/agent/mcp_agent/integrations/graphiti_integration.py new file mode 100644 index 0000000..1dc8d23 --- /dev/null +++ b/agent/mcp_agent/integrations/graphiti_integration.py @@ -0,0 +1,162 @@ +""" +Graphiti Knowledge Graph integration for the MCP Agent. +Provides a knowledge source that can be used by agents to access structured knowledge. +""" + +from typing import Dict, List, Any, Optional, Union +import os +import logging +from pydantic import BaseModel + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +class KnowledgeQuery(BaseModel): + """Model for knowledge graph queries""" + query: str + filters: Optional[Dict[str, Any]] = None + limit: int = 10 + +class KnowledgeEntity(BaseModel): + """Model for knowledge graph entities""" + id: str + type: str + properties: Dict[str, Any] + +class KnowledgeRelation(BaseModel): + """Model for knowledge graph relations""" + source_id: str + target_id: str + type: str + properties: Optional[Dict[str, Any]] = None + +class KnowledgeSubgraph(BaseModel): + """Model for knowledge subgraphs returned from queries""" + entities: List[KnowledgeEntity] + relations: List[KnowledgeRelation] + +class GraphitiKnowledgeSource: + """ + Integration with Graphiti Knowledge Graph. + Provides methods to query and update the knowledge graph. + """ + + def __init__(self, api_key: Optional[str] = None, endpoint: Optional[str] = None): + """ + Initialize the Graphiti knowledge source. + + Args: + api_key: API key for Graphiti. Defaults to GRAPHITI_API_KEY env var. + endpoint: Graphiti API endpoint. Defaults to GRAPHITI_ENDPOINT env var. + """ + self.api_key = api_key or os.getenv("GRAPHITI_API_KEY") + self.endpoint = endpoint or os.getenv("GRAPHITI_ENDPOINT", "https://api.graphiti.ai/v1") + + if not self.api_key: + logger.warning("No Graphiti API key provided. Some functionality may be limited.") + + # Initialize connection (placeholder for actual implementation) + self._initialize_connection() + + def _initialize_connection(self): + """Initialize connection to Graphiti (placeholder)""" + logger.info(f"Initializing connection to Graphiti at {self.endpoint}") + # In a real implementation, this would establish a connection or client + + async def query(self, query: Union[str, KnowledgeQuery]) -> KnowledgeSubgraph: + """ + Query the knowledge graph. + + Args: + query: Either a query string or a KnowledgeQuery object + + Returns: + A KnowledgeSubgraph containing matching entities and relations + """ + if isinstance(query, str): + query = KnowledgeQuery(query=query) + + logger.info(f"Querying knowledge graph: {query.query}") + + # Placeholder implementation - would actually call Graphiti API + # This simulates a simple response for testing + return KnowledgeSubgraph( + entities=[ + KnowledgeEntity( + id="entity1", + type="concept", + properties={"name": "Example Entity", "relevance": 0.95} + ) + ], + relations=[] + ) + + async def add_entity(self, entity: KnowledgeEntity) -> str: + """ + Add an entity to the knowledge graph. + + Args: + entity: The entity to add + + Returns: + The ID of the added entity + """ + logger.info(f"Adding entity to knowledge graph: {entity.id} ({entity.type})") + # Placeholder implementation + return entity.id + + async def add_relation(self, relation: KnowledgeRelation) -> bool: + """ + Add a relation to the knowledge graph. + + Args: + relation: The relation to add + + Returns: + True if successful, False otherwise + """ + logger.info(f"Adding relation to knowledge graph: {relation.source_id} -> {relation.target_id}") + # Placeholder implementation + return True + + async def get_entity(self, entity_id: str) -> Optional[KnowledgeEntity]: + """ + Get an entity by ID. + + Args: + entity_id: The ID of the entity to retrieve + + Returns: + The entity if found, None otherwise + """ + logger.info(f"Getting entity from knowledge graph: {entity_id}") + # Placeholder implementation + return KnowledgeEntity( + id=entity_id, + type="concept", + properties={"name": "Retrieved Entity"} + ) + + async def search_entities(self, query: str, entity_type: Optional[str] = None, limit: int = 10) -> List[KnowledgeEntity]: + """ + Search for entities matching a query. + + Args: + query: The search query + entity_type: Optional filter by entity type + limit: Maximum number of results to return + + Returns: + A list of matching entities + """ + logger.info(f"Searching entities: {query} (type={entity_type}, limit={limit})") + # Placeholder implementation + return [ + KnowledgeEntity( + id=f"result{i}", + type=entity_type or "concept", + properties={"name": f"Search Result {i}", "relevance": 0.9 - (i * 0.1)} + ) + for i in range(min(3, limit)) + ] \ No newline at end of file diff --git a/agent/mcp_agent/integrations/litellm_integration.py b/agent/mcp_agent/integrations/litellm_integration.py new file mode 100644 index 0000000..bd4515e --- /dev/null +++ b/agent/mcp_agent/integrations/litellm_integration.py @@ -0,0 +1,82 @@ +# Placeholder for LiteLLM integration + +import litellm +from typing import List, Dict, Any, Optional + +# Automatically load environment variables if python-dotenv is installed +try: + from dotenv import load_dotenv + load_dotenv() +except ImportError: + print("python-dotenv not found, skipping loading .env file") + +# Configure LiteLLM settings if needed (e.g., logging, exception handling) +# litellm.set_verbose = True + +class LiteLLMWrapper: + """Provides a unified asynchronous interface for interacting with various LLMs via LiteLLM.""" + + def __init__(self): + """Initializes the LiteLLM wrapper. + API keys are expected to be set as environment variables (e.g., OPENAI_API_KEY). + """ + # LiteLLM typically reads keys from environment variables automatically. + # No explicit initialization is usually needed here unless customizing behavior. + + async def get_llm_response( + self, + messages: List[Dict[str, str]], + model: str = "gpt-4o", # Default model, can be overridden + temperature: float = 0.7, + max_tokens: Optional[int] = None, + **kwargs: Any + ) -> str: + """Gets a response from the specified LLM using LiteLLM. + + Args: + messages: A list of message dictionaries (e.g., [{'role': 'user', 'content': '...'}]). + model: The name of the model to use (e.g., 'gpt-4', 'claude-3-opus-20240229'). + LiteLLM maps many common names automatically. + temperature: The sampling temperature. + max_tokens: The maximum number of tokens to generate. + **kwargs: Additional arguments to pass to the LiteLLM completion call. + + Returns: + The content of the response message from the LLM. + + Raises: + Exception: If the LiteLLM call fails. + """ + try: + print(f"Calling LiteLLM model: {model} with {len(messages)} messages.") # Basic logging + response = await litellm.acompletion( + model=model, + messages=messages, + temperature=temperature, + max_tokens=max_tokens, + **kwargs + ) + # Accessing response content might vary slightly based on LiteLLM version/model + # Usually response.choices[0].message.content + content = response.choices[0].message.content + print(f"LiteLLM response received (first 100 chars): {content[:100]}...") # Basic logging + return content + except Exception as e: + print(f"Error during LiteLLM call: {e}") # Basic error logging + # Consider more robust error handling/logging + raise + +# Example usage (optional, for testing) +# async def main(): +# wrapper = LiteLLMWrapper() +# messages = [{"role": "user", "content": "Tell me a joke about AI."}] +# try: +# response_content = await wrapper.get_llm_response(messages, model="gpt-3.5-turbo") +# print("\nLLM Response:") +# print(response_content) +# except Exception as e: +# print(f"Failed to get response: {e}") + +# if __name__ == "__main__": +# import asyncio +# asyncio.run(main()) \ No newline at end of file diff --git a/agent/mcp_agent/integrations/logfire_integration.py b/agent/mcp_agent/integrations/logfire_integration.py index 18ab81a..edeecaf 100644 --- a/agent/mcp_agent/integrations/logfire_integration.py +++ b/agent/mcp_agent/integrations/logfire_integration.py @@ -5,10 +5,18 @@ import os import logging -import logfire from typing import Optional, Dict, Any, List, Union from contextlib import contextmanager +# Try to import logfire, but provide a fallback if it's not installed +try: + import logfire + LOGFIRE_AVAILABLE = True +except ImportError: + LOGFIRE_AVAILABLE = False + logger = logging.getLogger(__name__) + logger.warning("Logfire module not found. Using fallback logging.") + # Configure basic logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -28,8 +36,9 @@ def __init__(self, project_name: Optional[str] = None, enabled: bool = True): enabled: Whether Logfire logging is enabled. Defaults to True. """ self.project_name = project_name or os.getenv("LOGFIRE_PROJECT", "kb-multi-agent") - self.enabled = enabled and self._check_logfire_configured() - + # Only enable if logfire is available and configured + self.enabled = enabled and LOGFIRE_AVAILABLE and self._check_logfire_configured() + if self.enabled: try: # Initialize Logfire @@ -44,6 +53,10 @@ def __init__(self, project_name: Optional[str] = None, enabled: bool = True): def _check_logfire_configured(self) -> bool: """Check if Logfire is configured in the environment.""" + # If logfire is not available, return False + if not LOGFIRE_AVAILABLE: + return False + # Check for Logfire configuration file or environment variables if os.path.exists(os.path.expanduser("~/.logfire/default.toml")): return True @@ -60,8 +73,10 @@ def log_event(self, event_name: str, data: Optional[Dict[str, Any]] = None) -> N data: Additional data to log with the event """ if not self.enabled: + # Fallback to standard logging + logger.info(f"Event: {event_name} - Data: {data}") return - + try: logfire.log(event_name, **(data or {})) except Exception as e: @@ -76,8 +91,10 @@ def log_error(self, error: Exception, context: Optional[Dict[str, Any]] = None) context: Additional context data """ if not self.enabled: + # Fallback to standard logging + logger.error(f"Error: {error} - Context: {context}") return - + try: logfire.error( error, @@ -97,9 +114,12 @@ def span(self, name: str, attributes: Optional[Dict[str, Any]] = None): attributes: Additional attributes for the span """ if not self.enabled: - yield + # Log span start/end with standard logging + logger.info(f"Span start: {name} - Attributes: {attributes}") + yield None + logger.info(f"Span end: {name}") return - + try: with logfire.span(name, **(attributes or {})) as span: yield span @@ -107,9 +127,9 @@ def span(self, name: str, attributes: Optional[Dict[str, Any]] = None): logger.warning(f"Failed to create Logfire span: {e}") yield None - def log_agent_request(self, - agent_id: str, - user_input: str, + def log_agent_request(self, + agent_id: str, + user_input: str, metadata: Optional[Dict[str, Any]] = None) -> None: """ Log an agent request. @@ -126,9 +146,9 @@ def log_agent_request(self, } self.log_event("agent_request", data) - def log_agent_response(self, - agent_id: str, - response: str, + def log_agent_response(self, + agent_id: str, + response: str, metadata: Optional[Dict[str, Any]] = None) -> None: """ Log an agent response. @@ -145,9 +165,9 @@ def log_agent_response(self, } self.log_event("agent_response", data) - def log_tool_call(self, - tool_name: str, - inputs: Dict[str, Any], + def log_tool_call(self, + tool_name: str, + inputs: Dict[str, Any], metadata: Optional[Dict[str, Any]] = None) -> None: """ Log a tool call. @@ -164,9 +184,9 @@ def log_tool_call(self, } self.log_event("tool_call", data) - def log_tool_result(self, - tool_name: str, - result: Any, + def log_tool_result(self, + tool_name: str, + result: Any, metadata: Optional[Dict[str, Any]] = None) -> None: """ Log a tool result. @@ -183,9 +203,9 @@ def log_tool_result(self, } self.log_event("tool_result", data) - def log_llm_call(self, - model: str, - prompt: Union[str, List[Dict[str, str]]], + def log_llm_call(self, + model: str, + prompt: Union[str, List[Dict[str, str]]], metadata: Optional[Dict[str, Any]] = None) -> None: """ Log an LLM call. @@ -197,7 +217,7 @@ def log_llm_call(self, """ # Convert prompt to string if it's a list of messages prompt_str = prompt if isinstance(prompt, str) else str(prompt) - + data = { "model": model, "prompt": prompt_str, @@ -205,9 +225,9 @@ def log_llm_call(self, } self.log_event("llm_call", data) - def log_llm_response(self, - model: str, - response: str, + def log_llm_response(self, + model: str, + response: str, metadata: Optional[Dict[str, Any]] = None) -> None: """ Log an LLM response. diff --git a/agent/mcp_agent/integrations/mem0_integration.py b/agent/mcp_agent/integrations/mem0_integration.py new file mode 100644 index 0000000..be7c69d --- /dev/null +++ b/agent/mcp_agent/integrations/mem0_integration.py @@ -0,0 +1,207 @@ +# Placeholder for Mem0 integration + +import os +from typing import List, Dict, Any, Optional + +# Use try-except for mem0 import in case it causes issues +try: + from mem0 import MemoryClient +except ImportError as e: + print(f"Failed to import mem0. Memory features will be unavailable: {e}") + MemoryClient = None # Define as None if import fails + +# Automatically load environment variables if python-dotenv is installed +try: + from dotenv import load_dotenv + load_dotenv() +except ImportError: + print("python-dotenv not found, skipping loading .env file") + +class Mem0MemoryManager: + """Manages interactions with the Mem0 service for semantic memory. + + Handles adding, searching, retrieving, updating, and deleting memories + associated with specific user IDs. + """ + + def __init__(self, api_key: Optional[str] = None): + """Initializes the Mem0 Memory Manager. + + Args: + api_key: The Mem0 API key. If None, attempts to read from + the MEM0_API_KEY environment variable. + + Raises: + ValueError: If Mem0 client could not be imported or if the API key is missing. + """ + if MemoryClient is None: + raise ValueError("Mem0ai library not installed or failed to import.") + + self.api_key = api_key or os.getenv("MEM0_API_KEY") + if not self.api_key: + raise ValueError("Mem0 API key not provided or found in environment variables.") + + # Initialize the client. Note: Mem0 client itself might not be async, + # but we wrap its calls in async methods for consistency in our agent architecture. + try: + self.client = MemoryClient(api_key=self.api_key) + print("Mem0 Client initialized successfully.") + except Exception as e: + print(f"Failed to initialize Mem0 Client: {e}") + raise ValueError(f"Failed to initialize Mem0 Client: {e}") from e + + async def add_memory(self, user_id: str, content: Optional[str] = None, messages: Optional[List[Dict[str, str]]] = None, metadata: Optional[dict] = None) -> Any: + """Adds a memory or a list of messages to Mem0 for a specific user. + + Provide either 'content' for a single memory string or 'messages' for a conversation list. + + Args: + user_id: The identifier for the user whose memory is being added. + content: A single string content for the memory. + messages: A list of message dictionaries (e.g., [{'role': 'user', 'content': '...'}]). + metadata: Optional metadata to associate with the memory. + + Returns: + The result from the Mem0 client's add operation. + + Raises: + ValueError: If neither content nor messages are provided. + Exception: If the Mem0 API call fails. + """ + if not content and not messages: + raise ValueError("Either 'content' or 'messages' must be provided.") + + add_kwargs = {"user_id": user_id} + if metadata: + add_kwargs["metadata"] = metadata + + try: + print(f"Adding memory for user_id: {user_id}") + if messages: + result = self.client.add(messages=messages, **add_kwargs) + else: # content must be provided due to the check above + result = self.client.add(content=content, **add_kwargs) + print(f"Mem0 add result: {result}") + return result + except Exception as e: + print(f"Error adding memory to Mem0 for user {user_id}: {e}") + raise + + async def search_memory(self, user_id: str, query: str, limit: int = 5, filters: Optional[Dict] = None) -> List[Dict[str, Any]]: + """Searches for relevant memories for a specific user based on a query. + + Args: + user_id: The identifier for the user whose memory is being searched. + query: The search query string. + limit: The maximum number of results to return. + filters: Optional filters to apply to the search. + + Returns: + A list of memory dictionaries matching the query. + + Raises: + Exception: If the Mem0 API call fails. + """ + try: + print(f"Searching memory for user_id: {user_id} with query: '{query[:50]}...'") + search_kwargs = {"user_id": user_id, "limit": limit} + if filters: + search_kwargs["filters"] = filters + + results = self.client.search(query=query, **search_kwargs) + print(f"Mem0 search returned {len(results)} results.") + return results + except Exception as e: + print(f"Error searching Mem0 memory for user {user_id}: {e}") + raise + + async def get_all_memories(self, user_id: str) -> List[Dict[str, Any]]: + """Retrieves all memories associated with a specific user. + + Args: + user_id: The identifier for the user. + + Returns: + A list of all memory dictionaries for the user. + + Raises: + Exception: If the Mem0 API call fails. + """ + try: + print(f"Getting all memories for user_id: {user_id}") + memories = self.client.get_all(user_id=user_id) + print(f"Mem0 get_all returned {len(memories)} memories.") + return memories + except Exception as e: + print(f"Error getting all memories from Mem0 for user {user_id}: {e}") + raise + + async def get_memory(self, memory_id: str) -> Optional[Dict[str, Any]]: + """Retrieves a specific memory by its ID. + + Args: + memory_id: The unique identifier of the memory. + + Returns: + The memory dictionary if found, otherwise potentially None (depends on client behavior). + + Raises: + Exception: If the Mem0 API call fails. + """ + try: + print(f"Getting memory with id: {memory_id}") + memory = self.client.get(memory_id=memory_id) + print(f"Mem0 get returned: {memory is not None}") + return memory + except Exception as e: + print(f"Error getting memory {memory_id} from Mem0: {e}") + raise + + async def update_memory(self, memory_id: str, data: Dict[str, Any]) -> Any: + """Updates an existing memory. + + Args: + memory_id: The ID of the memory to update. + data: A dictionary containing the fields to update. + + Returns: + The result from the Mem0 client's update operation. + + Raises: + Exception: If the Mem0 API call fails. + """ + try: + print(f"Updating memory with id: {memory_id}") + result = self.client.update(memory_id=memory_id, data=data) + print(f"Mem0 update result: {result}") + return result + except Exception as e: + print(f"Error updating memory {memory_id} in Mem0: {e}") + raise + + async def delete_memory(self, memory_id: str) -> Any: + """Deletes a specific memory by its ID. + + Args: + memory_id: The ID of the memory to delete. + + Returns: + The result from the Mem0 client's delete operation. + + Raises: + Exception: If the Mem0 API call fails. + """ + try: + print(f"Deleting memory with id: {memory_id}") + result = self.client.delete(memory_id=memory_id) + print(f"Mem0 delete result: {result}") + return result + except Exception as e: + print(f"Error deleting memory {memory_id} from Mem0: {e}") + raise + +# Note: The Mem0 Python client itself doesn't appear to be inherently async based on docs. +# These async wrappers execute the synchronous client calls within the async function, +# allowing them to be awaited in an async environment (like LangGraph/FastAPI). +# For true async IO with Mem0, one might need an async HTTP client (like httpx) +# to call the Mem0 REST API directly if the Python SDK doesn't support it natively. \ No newline at end of file diff --git a/agent/mcp_agent/integrations/memorysaver_manager.py b/agent/mcp_agent/integrations/memorysaver_manager.py new file mode 100644 index 0000000..38b1bf8 --- /dev/null +++ b/agent/mcp_agent/integrations/memorysaver_manager.py @@ -0,0 +1,91 @@ +from typing import List, Dict, Any, Optional +from .base_memory import BaseMemoryManager + +class MemorySaverManager(BaseMemoryManager): + """ + Wraps the legacy MemorySaver to conform to the BaseMemoryManager protocol. + Provides async, type-safe methods for agent memory operations. + """ + def __init__(self, storage_path: Optional[str] = None): + try: + from langgraph.checkpoint.memory import MemorySaver + except ImportError as e: + raise ImportError(f"MemorySaver could not be imported: {e}") + # Use a file path or in-memory by default + self.saver = MemorySaver(storage_path) if storage_path else MemorySaver() + + async def add_memory(self, user_id: str, content: Optional[str] = None, messages: Optional[List[Dict[str, str]]] = None, metadata: Optional[dict] = None) -> Any: + """ + Adds a memory for a user. MemorySaver expects a dict, so we store as a dict with user_id and content/messages. + """ + memory = {"user_id": user_id} + if content: + memory["content"] = content + if messages: + memory["messages"] = messages + if metadata: + memory["metadata"] = metadata + # MemorySaver is sync, so just call it directly + return self.saver.save(memory) + + async def search_memory(self, user_id: str, query: str, limit: int = 5, filters: Optional[Dict] = None) -> List[Dict[str, Any]]: + """ + Searches for memories for a user. MemorySaver does not support semantic search, so we filter by user_id and query in content/messages. + """ + all_memories = self.saver.load() + results = [] + for mem in all_memories: + if mem.get("user_id") != user_id: + continue + if query: + if (query in str(mem.get("content", ""))) or any(query in str(m.get("content", "")) for m in mem.get("messages", [])): + results.append(mem) + else: + results.append(mem) + if len(results) >= limit: + break + return results + + async def get_all_memories(self, user_id: str) -> List[Dict[str, Any]]: + """ + Returns all memories for a user. + """ + all_memories = self.saver.load() + return [mem for mem in all_memories if mem.get("user_id") == user_id] + + async def get_memory(self, memory_id: str) -> Optional[Dict[str, Any]]: + """ + Returns a memory by its id. MemorySaver does not natively support ids, so we treat the index as id. + """ + all_memories = self.saver.load() + try: + idx = int(memory_id) + return all_memories[idx] + except (ValueError, IndexError): + return None + + async def update_memory(self, memory_id: str, data: Dict[str, Any]) -> Any: + """ + Updates a memory by its id (index). Overwrites the memory at the given index. + """ + all_memories = self.saver.load() + try: + idx = int(memory_id) + all_memories[idx].update(data) + self.saver.save_all(all_memories) + return all_memories[idx] + except (ValueError, IndexError): + return None + + async def delete_memory(self, memory_id: str) -> Any: + """ + Deletes a memory by its id (index). + """ + all_memories = self.saver.load() + try: + idx = int(memory_id) + deleted = all_memories.pop(idx) + self.saver.save_all(all_memories) + return deleted + except (ValueError, IndexError): + return None \ No newline at end of file diff --git a/agent/mcp_agent/langgraph.json b/agent/mcp_agent/langgraph.json index 52112a2..7407394 100644 --- a/agent/mcp_agent/langgraph.json +++ b/agent/mcp_agent/langgraph.json @@ -1,9 +1,9 @@ { - "graphs": [ - { - "id": "mcp-agent", - "path": "./agent.py", - "variable": "graph" - } - ] + "python_version": "3.12", + "dockerfile_lines": [], + "dependencies": ["."], + "graphs": { + "mcp-agent": "./agent.py:graph" + }, + "env": ".env" } diff --git a/agent/mcp_agent/run_server.py b/agent/mcp_agent/run_server.py index 719f51b..a4908f0 100644 --- a/agent/mcp_agent/run_server.py +++ b/agent/mcp_agent/run_server.py @@ -7,6 +7,7 @@ import sys import argparse import subprocess +import uvicorn from pathlib import Path def main(): @@ -14,27 +15,51 @@ def main(): parser = argparse.ArgumentParser(description="Run the MCP Agent server") parser.add_argument("--host", default="localhost", help="Host to run the server on") parser.add_argument("--port", default="8123", help="Port to run the server on") + parser.add_argument("--custom", action="store_true", help="Use custom server with health endpoint") args = parser.parse_args() - + # Get the current directory (mcp_agent) current_dir = Path(__file__).parent - + # Get the parent directory (agent) agent_dir = current_dir.parent - + # Change to the agent directory os.chdir(agent_dir) - - # Run the langgraph dev command - cmd = [ - "langgraph", "dev", - "--config", "langgraph.json", - "--host", args.host, - "--port", args.port, - "--no-browser" - ] - - subprocess.run(cmd) + + if args.custom: + # Run the custom server with health endpoint + try: + print(f"Starting custom MCP Agent server on http://{args.host}:{args.port}") + print(f"Health endpoint available at http://{args.host}:{args.port}/health") + uvicorn.run( + "mcp_agent.custom_server:create_app", + host=args.host, + port=int(args.port), + factory=True, + reload=True + ) + except KeyboardInterrupt: + print("\nServer stopped by user.") + else: + # Run the standard langgraph dev command + cmd = [ + "langgraph", "dev", + "--config", "langgraph.json", + "--host", args.host, + "--port", args.port, + "--no-browser" + ] + + try: + # Use subprocess.run with proper error handling + subprocess.run(cmd, check=True) + except KeyboardInterrupt: + print("\nServer stopped by user.") + except subprocess.CalledProcessError as e: + print(f"\nError running langgraph server: {e}") + except Exception as e: + print(f"\nUnexpected error: {e}") if __name__ == "__main__": main() diff --git a/agent/mcp_agent/test_agent_factory.py b/agent/mcp_agent/test_agent_factory.py new file mode 100644 index 0000000..cbc65c3 --- /dev/null +++ b/agent/mcp_agent/test_agent_factory.py @@ -0,0 +1,53 @@ +import pytest +import os +# Removed unused import: from mcp_agent.integrations.mem0_integration import Mem0MemoryManager +from mcp_agent.integrations.memorysaver_manager import MemorySaverManager +from mcp_agent.integrations.litellm_integration import LiteLLMWrapper +from mcp_agent.integrations.a2a_integration import A2ACommunicator +from mcp_agent.integrations.a2a_protocols import A2AMessage, TaskRequestPayload +from mcp_agent.agent_factory import AgentFactory + +pytestmark = pytest.mark.asyncio + +class DummyAgent: + def __init__(self, memory, llm, a2a, knowledge, **kwargs): + self.memory = memory + self.llm = llm + self.a2a = a2a + self.knowledge = knowledge + self.kwargs = kwargs + +async def test_memorysaver_manager(tmp_path): + manager = MemorySaverManager(storage_path=str(tmp_path / "memsaver.json")) + await manager.add_memory("user1", content="hello world") + results = await manager.search_memory("user1", query="hello") + assert results and results[0]["content"] == "hello world" + all_memories = await manager.get_all_memories("user1") + assert len(all_memories) == 1 + await manager.update_memory("0", {"content": "updated"}) + updated = await manager.get_memory("0") + assert updated["content"] == "updated" + await manager.delete_memory("0") + assert await manager.get_memory("0") is None + +async def test_a2a_communicator(): + comm = A2ACommunicator() + msg = A2AMessage( + sender_agent_id="a1", + recipient_agent_id="a2", + message_type="task_request", + payload=TaskRequestPayload(task_type="echo", parameters={"msg": "hi"}), + ) + await comm.send_message(msg) + received = await comm.receive_message("a2", timeout=1) + assert received and received.sender_agent_id == "a1" + +async def test_agent_factory_memorysaver(): + os.environ["MEMORY_BACKEND"] = "memorysaver" + factory = AgentFactory() + agent = factory.create_agent(DummyAgent, agent_id="test1") + assert isinstance(agent.memory, MemorySaverManager) + assert isinstance(agent.llm, LiteLLMWrapper) + assert isinstance(agent.a2a, A2ACommunicator) + +# Add more tests for Mem0MemoryManager and LiteLLMWrapper as needed (mock external APIs) \ No newline at end of file diff --git a/agent/poetry.lock b/agent/poetry.lock index 294657a..52a1764 100644 --- a/agent/poetry.lock +++ b/agent/poetry.lock @@ -14,93 +14,93 @@ files = [ [[package]] name = "aiohttp" -version = "3.11.13" +version = "3.11.18" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "aiohttp-3.11.13-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a4fe27dbbeec445e6e1291e61d61eb212ee9fed6e47998b27de71d70d3e8777d"}, - {file = "aiohttp-3.11.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9e64ca2dbea28807f8484c13f684a2f761e69ba2640ec49dacd342763cc265ef"}, - {file = "aiohttp-3.11.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9840be675de208d1f68f84d578eaa4d1a36eee70b16ae31ab933520c49ba1325"}, - {file = "aiohttp-3.11.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28a772757c9067e2aee8a6b2b425d0efaa628c264d6416d283694c3d86da7689"}, - {file = "aiohttp-3.11.13-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b88aca5adbf4625e11118df45acac29616b425833c3be7a05ef63a6a4017bfdb"}, - {file = "aiohttp-3.11.13-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce10ddfbe26ed5856d6902162f71b8fe08545380570a885b4ab56aecfdcb07f4"}, - {file = "aiohttp-3.11.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa48dac27f41b36735c807d1ab093a8386701bbf00eb6b89a0f69d9fa26b3671"}, - {file = "aiohttp-3.11.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89ce611b1eac93ce2ade68f1470889e0173d606de20c85a012bfa24be96cf867"}, - {file = "aiohttp-3.11.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:78e4dd9c34ec7b8b121854eb5342bac8b02aa03075ae8618b6210a06bbb8a115"}, - {file = "aiohttp-3.11.13-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:66047eacbc73e6fe2462b77ce39fc170ab51235caf331e735eae91c95e6a11e4"}, - {file = "aiohttp-3.11.13-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5ad8f1c19fe277eeb8bc45741c6d60ddd11d705c12a4d8ee17546acff98e0802"}, - {file = "aiohttp-3.11.13-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64815c6f02e8506b10113ddbc6b196f58dbef135751cc7c32136df27b736db09"}, - {file = "aiohttp-3.11.13-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:967b93f21b426f23ca37329230d5bd122f25516ae2f24a9cea95a30023ff8283"}, - {file = "aiohttp-3.11.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf1f31f83d16ec344136359001c5e871915c6ab685a3d8dee38e2961b4c81730"}, - {file = "aiohttp-3.11.13-cp310-cp310-win32.whl", hash = "sha256:00c8ac69e259c60976aa2edae3f13d9991cf079aaa4d3cd5a49168ae3748dee3"}, - {file = "aiohttp-3.11.13-cp310-cp310-win_amd64.whl", hash = "sha256:90d571c98d19a8b6e793b34aa4df4cee1e8fe2862d65cc49185a3a3d0a1a3996"}, - {file = "aiohttp-3.11.13-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b35aab22419ba45f8fc290d0010898de7a6ad131e468ffa3922b1b0b24e9d2e"}, - {file = "aiohttp-3.11.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81cba651db8795f688c589dd11a4fbb834f2e59bbf9bb50908be36e416dc760"}, - {file = "aiohttp-3.11.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f55d0f242c2d1fcdf802c8fabcff25a9d85550a4cf3a9cf5f2a6b5742c992839"}, - {file = "aiohttp-3.11.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4bea08a6aad9195ac9b1be6b0c7e8a702a9cec57ce6b713698b4a5afa9c2e33"}, - {file = "aiohttp-3.11.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6070bcf2173a7146bb9e4735b3c62b2accba459a6eae44deea0eb23e0035a23"}, - {file = "aiohttp-3.11.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:718d5deb678bc4b9d575bfe83a59270861417da071ab44542d0fcb6faa686636"}, - {file = "aiohttp-3.11.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f6b2c5b4a4d22b8fb2c92ac98e0747f5f195e8e9448bfb7404cd77e7bfa243f"}, - {file = "aiohttp-3.11.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:747ec46290107a490d21fe1ff4183bef8022b848cf9516970cb31de6d9460088"}, - {file = "aiohttp-3.11.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:01816f07c9cc9d80f858615b1365f8319d6a5fd079cd668cc58e15aafbc76a54"}, - {file = "aiohttp-3.11.13-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:a08ad95fcbd595803e0c4280671d808eb170a64ca3f2980dd38e7a72ed8d1fea"}, - {file = "aiohttp-3.11.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c97be90d70f7db3aa041d720bfb95f4869d6063fcdf2bb8333764d97e319b7d0"}, - {file = "aiohttp-3.11.13-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ab915a57c65f7a29353c8014ac4be685c8e4a19e792a79fe133a8e101111438e"}, - {file = "aiohttp-3.11.13-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:35cda4e07f5e058a723436c4d2b7ba2124ab4e0aa49e6325aed5896507a8a42e"}, - {file = "aiohttp-3.11.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:af55314407714fe77a68a9ccaab90fdb5deb57342585fd4a3a8102b6d4370080"}, - {file = "aiohttp-3.11.13-cp311-cp311-win32.whl", hash = "sha256:42d689a5c0a0c357018993e471893e939f555e302313d5c61dfc566c2cad6185"}, - {file = "aiohttp-3.11.13-cp311-cp311-win_amd64.whl", hash = "sha256:b73a2b139782a07658fbf170fe4bcdf70fc597fae5ffe75e5b67674c27434a9f"}, - {file = "aiohttp-3.11.13-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2eabb269dc3852537d57589b36d7f7362e57d1ece308842ef44d9830d2dc3c90"}, - {file = "aiohttp-3.11.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b77ee42addbb1c36d35aca55e8cc6d0958f8419e458bb70888d8c69a4ca833d"}, - {file = "aiohttp-3.11.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55789e93c5ed71832e7fac868167276beadf9877b85697020c46e9a75471f55f"}, - {file = "aiohttp-3.11.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c929f9a7249a11e4aa5c157091cfad7f49cc6b13f4eecf9b747104befd9f56f2"}, - {file = "aiohttp-3.11.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d33851d85537bbf0f6291ddc97926a754c8f041af759e0aa0230fe939168852b"}, - {file = "aiohttp-3.11.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9229d8613bd8401182868fe95688f7581673e1c18ff78855671a4b8284f47bcb"}, - {file = "aiohttp-3.11.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:669dd33f028e54fe4c96576f406ebb242ba534dd3a981ce009961bf49960f117"}, - {file = "aiohttp-3.11.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c1b20a1ace54af7db1f95af85da530fe97407d9063b7aaf9ce6a32f44730778"}, - {file = "aiohttp-3.11.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5724cc77f4e648362ebbb49bdecb9e2b86d9b172c68a295263fa072e679ee69d"}, - {file = "aiohttp-3.11.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:aa36c35e94ecdb478246dd60db12aba57cfcd0abcad43c927a8876f25734d496"}, - {file = "aiohttp-3.11.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9b5b37c863ad5b0892cc7a4ceb1e435e5e6acd3f2f8d3e11fa56f08d3c67b820"}, - {file = "aiohttp-3.11.13-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e06cf4852ce8c4442a59bae5a3ea01162b8fcb49ab438d8548b8dc79375dad8a"}, - {file = "aiohttp-3.11.13-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5194143927e494616e335d074e77a5dac7cd353a04755330c9adc984ac5a628e"}, - {file = "aiohttp-3.11.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:afcb6b275c2d2ba5d8418bf30a9654fa978b4f819c2e8db6311b3525c86fe637"}, - {file = "aiohttp-3.11.13-cp312-cp312-win32.whl", hash = "sha256:7104d5b3943c6351d1ad7027d90bdd0ea002903e9f610735ac99df3b81f102ee"}, - {file = "aiohttp-3.11.13-cp312-cp312-win_amd64.whl", hash = "sha256:47dc018b1b220c48089b5b9382fbab94db35bef2fa192995be22cbad3c5730c8"}, - {file = "aiohttp-3.11.13-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9862d077b9ffa015dbe3ce6c081bdf35135948cb89116e26667dd183550833d1"}, - {file = "aiohttp-3.11.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fbfef0666ae9e07abfa2c54c212ac18a1f63e13e0760a769f70b5717742f3ece"}, - {file = "aiohttp-3.11.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93a1f7d857c4fcf7cabb1178058182c789b30d85de379e04f64c15b7e88d66fb"}, - {file = "aiohttp-3.11.13-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba40b7ae0f81c7029583a338853f6607b6d83a341a3dcde8bed1ea58a3af1df9"}, - {file = "aiohttp-3.11.13-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5b95787335c483cd5f29577f42bbe027a412c5431f2f80a749c80d040f7ca9f"}, - {file = "aiohttp-3.11.13-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7d474c5c1f0b9405c1565fafdc4429fa7d986ccbec7ce55bc6a330f36409cad"}, - {file = "aiohttp-3.11.13-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e83fb1991e9d8982b3b36aea1e7ad27ea0ce18c14d054c7a404d68b0319eebb"}, - {file = "aiohttp-3.11.13-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4586a68730bd2f2b04a83e83f79d271d8ed13763f64b75920f18a3a677b9a7f0"}, - {file = "aiohttp-3.11.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fe4eb0e7f50cdb99b26250d9328faef30b1175a5dbcfd6d0578d18456bac567"}, - {file = "aiohttp-3.11.13-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2a8a6bc19818ac3e5596310ace5aa50d918e1ebdcc204dc96e2f4d505d51740c"}, - {file = "aiohttp-3.11.13-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f27eec42f6c3c1df09cfc1f6786308f8b525b8efaaf6d6bd76c1f52c6511f6a"}, - {file = "aiohttp-3.11.13-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2a4a13dfbb23977a51853b419141cd0a9b9573ab8d3a1455c6e63561387b52ff"}, - {file = "aiohttp-3.11.13-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:02876bf2f69b062584965507b07bc06903c2dc93c57a554b64e012d636952654"}, - {file = "aiohttp-3.11.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b992778d95b60a21c4d8d4a5f15aaab2bd3c3e16466a72d7f9bfd86e8cea0d4b"}, - {file = "aiohttp-3.11.13-cp313-cp313-win32.whl", hash = "sha256:507ab05d90586dacb4f26a001c3abf912eb719d05635cbfad930bdbeb469b36c"}, - {file = "aiohttp-3.11.13-cp313-cp313-win_amd64.whl", hash = "sha256:5ceb81a4db2decdfa087381b5fc5847aa448244f973e5da232610304e199e7b2"}, - {file = "aiohttp-3.11.13-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:51c3ff9c7a25f3cad5c09d9aacbc5aefb9267167c4652c1eb737989b554fe278"}, - {file = "aiohttp-3.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e271beb2b1dabec5cd84eb488bdabf9758d22ad13471e9c356be07ad139b3012"}, - {file = "aiohttp-3.11.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0e9eb7e5764abcb49f0e2bd8f5731849b8728efbf26d0cac8e81384c95acec3f"}, - {file = "aiohttp-3.11.13-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baae005092e3f200de02699314ac8933ec20abf998ec0be39448f6605bce93df"}, - {file = "aiohttp-3.11.13-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1982c98ac62c132d2b773d50e2fcc941eb0b8bad3ec078ce7e7877c4d5a2dce7"}, - {file = "aiohttp-3.11.13-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2b25b2eeb35707113b2d570cadc7c612a57f1c5d3e7bb2b13870fe284e08fc0"}, - {file = "aiohttp-3.11.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b27961d65639128336b7a7c3f0046dcc62a9443d5ef962e3c84170ac620cec47"}, - {file = "aiohttp-3.11.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a01fe9f1e05025eacdd97590895e2737b9f851d0eb2e017ae9574d9a4f0b6252"}, - {file = "aiohttp-3.11.13-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa1fb1b61881c8405829c50e9cc5c875bfdbf685edf57a76817dfb50643e4a1a"}, - {file = "aiohttp-3.11.13-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:25de43bb3cf83ad83efc8295af7310219af6dbe4c543c2e74988d8e9c8a2a917"}, - {file = "aiohttp-3.11.13-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fe7065e2215e4bba63dc00db9ae654c1ba3950a5fff691475a32f511142fcddb"}, - {file = "aiohttp-3.11.13-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:7836587eef675a17d835ec3d98a8c9acdbeb2c1d72b0556f0edf4e855a25e9c1"}, - {file = "aiohttp-3.11.13-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:85fa0b18558eb1427090912bd456a01f71edab0872f4e0f9e4285571941e4090"}, - {file = "aiohttp-3.11.13-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a86dc177eb4c286c19d1823ac296299f59ed8106c9536d2b559f65836e0fb2c6"}, - {file = "aiohttp-3.11.13-cp39-cp39-win32.whl", hash = "sha256:684eea71ab6e8ade86b9021bb62af4bf0881f6be4e926b6b5455de74e420783a"}, - {file = "aiohttp-3.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:82c249f2bfa5ecbe4a1a7902c81c0fba52ed9ebd0176ab3047395d02ad96cfcb"}, - {file = "aiohttp-3.11.13.tar.gz", hash = "sha256:8ce789231404ca8fff7f693cdce398abf6d90fd5dae2b1847477196c243b1fbb"}, + {file = "aiohttp-3.11.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:96264854fedbea933a9ca4b7e0c745728f01380691687b7365d18d9e977179c4"}, + {file = "aiohttp-3.11.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9602044ff047043430452bc3a2089743fa85da829e6fc9ee0025351d66c332b6"}, + {file = "aiohttp-3.11.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5691dc38750fcb96a33ceef89642f139aa315c8a193bbd42a0c33476fd4a1609"}, + {file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:554c918ec43f8480b47a5ca758e10e793bd7410b83701676a4782672d670da55"}, + {file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a4076a2b3ba5b004b8cffca6afe18a3b2c5c9ef679b4d1e9859cf76295f8d4f"}, + {file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:767a97e6900edd11c762be96d82d13a1d7c4fc4b329f054e88b57cdc21fded94"}, + {file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0ddc9337a0fb0e727785ad4f41163cc314376e82b31846d3835673786420ef1"}, + {file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f414f37b244f2a97e79b98d48c5ff0789a0b4b4609b17d64fa81771ad780e415"}, + {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fdb239f47328581e2ec7744ab5911f97afb10752332a6dd3d98e14e429e1a9e7"}, + {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f2c50bad73ed629cc326cc0f75aed8ecfb013f88c5af116f33df556ed47143eb"}, + {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a8d8f20c39d3fa84d1c28cdb97f3111387e48209e224408e75f29c6f8e0861d"}, + {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:106032eaf9e62fd6bc6578c8b9e6dc4f5ed9a5c1c7fb2231010a1b4304393421"}, + {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:b491e42183e8fcc9901d8dcd8ae644ff785590f1727f76ca86e731c61bfe6643"}, + {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad8c745ff9460a16b710e58e06a9dec11ebc0d8f4dd82091cefb579844d69868"}, + {file = "aiohttp-3.11.18-cp310-cp310-win32.whl", hash = "sha256:8e57da93e24303a883146510a434f0faf2f1e7e659f3041abc4e3fb3f6702a9f"}, + {file = "aiohttp-3.11.18-cp310-cp310-win_amd64.whl", hash = "sha256:cc93a4121d87d9f12739fc8fab0a95f78444e571ed63e40bfc78cd5abe700ac9"}, + {file = "aiohttp-3.11.18-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:427fdc56ccb6901ff8088544bde47084845ea81591deb16f957897f0f0ba1be9"}, + {file = "aiohttp-3.11.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c828b6d23b984255b85b9b04a5b963a74278b7356a7de84fda5e3b76866597b"}, + {file = "aiohttp-3.11.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c2eaa145bb36b33af1ff2860820ba0589e165be4ab63a49aebfd0981c173b66"}, + {file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d518ce32179f7e2096bf4e3e8438cf445f05fedd597f252de9f54c728574756"}, + {file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0700055a6e05c2f4711011a44364020d7a10fbbcd02fbf3e30e8f7e7fddc8717"}, + {file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bd1cde83e4684324e6ee19adfc25fd649d04078179890be7b29f76b501de8e4"}, + {file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73b8870fe1c9a201b8c0d12c94fe781b918664766728783241a79e0468427e4f"}, + {file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25557982dd36b9e32c0a3357f30804e80790ec2c4d20ac6bcc598533e04c6361"}, + {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7e889c9df381a2433802991288a61e5a19ceb4f61bd14f5c9fa165655dcb1fd1"}, + {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9ea345fda05bae217b6cce2acf3682ce3b13d0d16dd47d0de7080e5e21362421"}, + {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9f26545b9940c4b46f0a9388fd04ee3ad7064c4017b5a334dd450f616396590e"}, + {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3a621d85e85dccabd700294494d7179ed1590b6d07a35709bb9bd608c7f5dd1d"}, + {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9c23fd8d08eb9c2af3faeedc8c56e134acdaf36e2117ee059d7defa655130e5f"}, + {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9e6b0e519067caa4fd7fb72e3e8002d16a68e84e62e7291092a5433763dc0dd"}, + {file = "aiohttp-3.11.18-cp311-cp311-win32.whl", hash = "sha256:122f3e739f6607e5e4c6a2f8562a6f476192a682a52bda8b4c6d4254e1138f4d"}, + {file = "aiohttp-3.11.18-cp311-cp311-win_amd64.whl", hash = "sha256:e6f3c0a3a1e73e88af384b2e8a0b9f4fb73245afd47589df2afcab6b638fa0e6"}, + {file = "aiohttp-3.11.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:63d71eceb9cad35d47d71f78edac41fcd01ff10cacaa64e473d1aec13fa02df2"}, + {file = "aiohttp-3.11.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d1929da615840969929e8878d7951b31afe0bac883d84418f92e5755d7b49508"}, + {file = "aiohttp-3.11.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d0aebeb2392f19b184e3fdd9e651b0e39cd0f195cdb93328bd124a1d455cd0e"}, + {file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3849ead845e8444f7331c284132ab314b4dac43bfae1e3cf350906d4fff4620f"}, + {file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e8452ad6b2863709f8b3d615955aa0807bc093c34b8e25b3b52097fe421cb7f"}, + {file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b8d2b42073611c860a37f718b3d61ae8b4c2b124b2e776e2c10619d920350ec"}, + {file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fbf91f6a0ac317c0a07eb328a1384941872f6761f2e6f7208b63c4cc0a7ff6"}, + {file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ff5625413fec55216da5eaa011cf6b0a2ed67a565914a212a51aa3755b0009"}, + {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7f33a92a2fde08e8c6b0c61815521324fc1612f397abf96eed86b8e31618fdb4"}, + {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:11d5391946605f445ddafda5eab11caf310f90cdda1fd99865564e3164f5cff9"}, + {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3cc314245deb311364884e44242e00c18b5896e4fe6d5f942e7ad7e4cb640adb"}, + {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f421843b0f70740772228b9e8093289924359d306530bcd3926f39acbe1adda"}, + {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e220e7562467dc8d589e31c1acd13438d82c03d7f385c9cd41a3f6d1d15807c1"}, + {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ab2ef72f8605046115bc9aa8e9d14fd49086d405855f40b79ed9e5c1f9f4faea"}, + {file = "aiohttp-3.11.18-cp312-cp312-win32.whl", hash = "sha256:12a62691eb5aac58d65200c7ae94d73e8a65c331c3a86a2e9670927e94339ee8"}, + {file = "aiohttp-3.11.18-cp312-cp312-win_amd64.whl", hash = "sha256:364329f319c499128fd5cd2d1c31c44f234c58f9b96cc57f743d16ec4f3238c8"}, + {file = "aiohttp-3.11.18-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:474215ec618974054cf5dc465497ae9708543cbfc312c65212325d4212525811"}, + {file = "aiohttp-3.11.18-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ced70adf03920d4e67c373fd692123e34d3ac81dfa1c27e45904a628567d804"}, + {file = "aiohttp-3.11.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d9f6c0152f8d71361905aaf9ed979259537981f47ad099c8b3d81e0319814bd"}, + {file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a35197013ed929c0aed5c9096de1fc5a9d336914d73ab3f9df14741668c0616c"}, + {file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:540b8a1f3a424f1af63e0af2d2853a759242a1769f9f1ab053996a392bd70118"}, + {file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9e6710ebebfce2ba21cee6d91e7452d1125100f41b906fb5af3da8c78b764c1"}, + {file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8af2ef3b4b652ff109f98087242e2ab974b2b2b496304063585e3d78de0b000"}, + {file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28c3f975e5ae3dbcbe95b7e3dcd30e51da561a0a0f2cfbcdea30fc1308d72137"}, + {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c28875e316c7b4c3e745172d882d8a5c835b11018e33432d281211af35794a93"}, + {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:13cd38515568ae230e1ef6919e2e33da5d0f46862943fcda74e7e915096815f3"}, + {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0e2a92101efb9f4c2942252c69c63ddb26d20f46f540c239ccfa5af865197bb8"}, + {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e6d3e32b8753c8d45ac550b11a1090dd66d110d4ef805ffe60fa61495360b3b2"}, + {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ea4cf2488156e0f281f93cc2fd365025efcba3e2d217cbe3df2840f8c73db261"}, + {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d4df95ad522c53f2b9ebc07f12ccd2cb15550941e11a5bbc5ddca2ca56316d7"}, + {file = "aiohttp-3.11.18-cp313-cp313-win32.whl", hash = "sha256:cdd1bbaf1e61f0d94aced116d6e95fe25942f7a5f42382195fd9501089db5d78"}, + {file = "aiohttp-3.11.18-cp313-cp313-win_amd64.whl", hash = "sha256:bdd619c27e44382cf642223f11cfd4d795161362a5a1fc1fa3940397bc89db01"}, + {file = "aiohttp-3.11.18-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:469ac32375d9a716da49817cd26f1916ec787fc82b151c1c832f58420e6d3533"}, + {file = "aiohttp-3.11.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3cec21dd68924179258ae14af9f5418c1ebdbba60b98c667815891293902e5e0"}, + {file = "aiohttp-3.11.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b426495fb9140e75719b3ae70a5e8dd3a79def0ae3c6c27e012fc59f16544a4a"}, + {file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad2f41203e2808616292db5d7170cccf0c9f9c982d02544443c7eb0296e8b0c7"}, + {file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc0ae0a5e9939e423e065a3e5b00b24b8379f1db46046d7ab71753dfc7dd0e1"}, + {file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe7cdd3f7d1df43200e1c80f1aed86bb36033bf65e3c7cf46a2b97a253ef8798"}, + {file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5199be2a2f01ffdfa8c3a6f5981205242986b9e63eb8ae03fd18f736e4840721"}, + {file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ccec9e72660b10f8e283e91aa0295975c7bd85c204011d9f5eb69310555cf30"}, + {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1596ebf17e42e293cbacc7a24c3e0dc0f8f755b40aff0402cb74c1ff6baec1d3"}, + {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:eab7b040a8a873020113ba814b7db7fa935235e4cbaf8f3da17671baa1024863"}, + {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5d61df4a05476ff891cff0030329fee4088d40e4dc9b013fac01bc3c745542c2"}, + {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:46533e6792e1410f9801d09fd40cbbff3f3518d1b501d6c3c5b218f427f6ff08"}, + {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c1b90407ced992331dd6d4f1355819ea1c274cc1ee4d5b7046c6761f9ec11829"}, + {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a2fd04ae4971b914e54fe459dd7edbbd3f2ba875d69e057d5e3c8e8cac094935"}, + {file = "aiohttp-3.11.18-cp39-cp39-win32.whl", hash = "sha256:b2f317d1678002eee6fe85670039fb34a757972284614638f82b903a03feacdc"}, + {file = "aiohttp-3.11.18-cp39-cp39-win_amd64.whl", hash = "sha256:5e7007b8d1d09bce37b54111f593d173691c530b80f27c6493b928dabed9e6ef"}, + {file = "aiohttp-3.11.18.tar.gz", hash = "sha256:ae856e1138612b7e412db63b7708735cff4d38d0399f6a5435d3dac2669f558a"}, ] [package.dependencies] @@ -224,6 +224,22 @@ docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphi tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] +[[package]] +name = "autoflake" +version = "2.3.1" +description = "Removes unused imports and unused variables" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "autoflake-2.3.1-py3-none-any.whl", hash = "sha256:3ae7495db9084b7b32818b4140e6dc4fc280b712fb414f5b8fe57b0a8e85a840"}, + {file = "autoflake-2.3.1.tar.gz", hash = "sha256:c98b75dc5b0a86459c4f01a1d32ac7eb4338ec4317a4469515ff1e687ecd909e"}, +] + +[package.dependencies] +pyflakes = ">=3.0.0" +tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} + [[package]] name = "certifi" version = "2025.1.31" @@ -1220,21 +1236,22 @@ langchain-core = ">=0.3.34,<1.0.0" [[package]] name = "langgraph" -version = "0.3.5" +version = "0.3.31" description = "Building stateful, multi-actor applications with LLMs" optional = false python-versions = "<4.0,>=3.9.0" groups = ["main"] files = [ - {file = "langgraph-0.3.5-py3-none-any.whl", hash = "sha256:be313ec300633c857873ea3e44aece4dd7d0b11f131d385108b359d377a85bf7"}, - {file = "langgraph-0.3.5.tar.gz", hash = "sha256:7c0d8e61aa02578b41036c9f7a599ccba2562d269f66ef76bacbba47a99a7eca"}, + {file = "langgraph-0.3.31-py3-none-any.whl", hash = "sha256:f42a6850d03696f2a54d3c833db39aa201cfda78217846a5b8936ad95fe41e6c"}, + {file = "langgraph-0.3.31.tar.gz", hash = "sha256:c76bbc8f26604929d6c2520d937b5602d5bb1dde2c0580398d015f7f6841f14c"}, ] [package.dependencies] langchain-core = ">=0.1,<0.4" langgraph-checkpoint = ">=2.0.10,<3.0.0" -langgraph-prebuilt = ">=0.1.1,<0.2" +langgraph-prebuilt = ">=0.1.8,<0.2" langgraph-sdk = ">=0.1.42,<0.2.0" +xxhash = ">=3.5.0,<4.0.0" [[package]] name = "langgraph-api" @@ -1269,19 +1286,19 @@ watchfiles = ">=0.13" [[package]] name = "langgraph-checkpoint" -version = "2.0.18" +version = "2.0.24" description = "Library with base interfaces for LangGraph checkpoint savers." optional = false python-versions = "<4.0.0,>=3.9.0" groups = ["main"] files = [ - {file = "langgraph_checkpoint-2.0.18-py3-none-any.whl", hash = "sha256:941de442e5a893a6cabb8c3845f03159301b85f63ff4e8f2b308f7dfd96a3f59"}, - {file = "langgraph_checkpoint-2.0.18.tar.gz", hash = "sha256:2822eedd028b454b7bfebfb7e04347aed1b64db97dedb7eb68ef0fb42641606d"}, + {file = "langgraph_checkpoint-2.0.24-py3-none-any.whl", hash = "sha256:3836e2909ef2387d1fa8d04ee3e2a353f980d519fd6c649af352676dc73d66b8"}, + {file = "langgraph_checkpoint-2.0.24.tar.gz", hash = "sha256:9596dad332344e7e871257be464df8a07c2e9bac66143081b11b9422b0167e5b"}, ] [package.dependencies] langchain-core = ">=0.2.38,<0.4" -msgpack = ">=1.1.0,<2.0.0" +ormsgpack = ">=1.8.0,<2.0.0" [[package]] name = "langgraph-cli" @@ -1305,14 +1322,14 @@ inmem = ["langgraph-api (>=0.0.27,<0.1.0) ; python_version >= \"3.11\" and pytho [[package]] name = "langgraph-prebuilt" -version = "0.1.2" +version = "0.1.8" description = "Library with high-level APIs for creating and executing LangGraph agents and tools." optional = false python-versions = "<4.0.0,>=3.9.0" groups = ["main"] files = [ - {file = "langgraph_prebuilt-0.1.2-py3-none-any.whl", hash = "sha256:32028c4c4370576748e6c2e075cab1e13b5e3f2c196a390d71cacfb455212311"}, - {file = "langgraph_prebuilt-0.1.2.tar.gz", hash = "sha256:cfa7e54006d45e8f3d034ee88fa1d457c381bf6a2a0de0e64c5d3a776659e6d0"}, + {file = "langgraph_prebuilt-0.1.8-py3-none-any.whl", hash = "sha256:ae97b828ae00be2cefec503423aa782e1bff165e9b94592e224da132f2526968"}, + {file = "langgraph_prebuilt-0.1.8.tar.gz", hash = "sha256:4de7659151829b2b955b6798df6800e580e617782c15c2c5b29b139697491831"}, ] [package.dependencies] @@ -1446,80 +1463,6 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] -[[package]] -name = "msgpack" -version = "1.1.0" -description = "MessagePack serializer" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, - {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, - {file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5"}, - {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5"}, - {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e"}, - {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b"}, - {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f"}, - {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68"}, - {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b"}, - {file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044"}, - {file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f"}, - {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7"}, - {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa"}, - {file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701"}, - {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6"}, - {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59"}, - {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0"}, - {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e"}, - {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6"}, - {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5"}, - {file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88"}, - {file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788"}, - {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"}, - {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"}, - {file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"}, - {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"}, - {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"}, - {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"}, - {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"}, - {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"}, - {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"}, - {file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"}, - {file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"}, - {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf"}, - {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330"}, - {file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734"}, - {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e"}, - {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca"}, - {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915"}, - {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d"}, - {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434"}, - {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c"}, - {file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc"}, - {file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f"}, - {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec"}, - {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96"}, - {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870"}, - {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7"}, - {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb"}, - {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f"}, - {file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b"}, - {file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb"}, - {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1"}, - {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48"}, - {file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c"}, - {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468"}, - {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74"}, - {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846"}, - {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346"}, - {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b"}, - {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8"}, - {file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd"}, - {file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325"}, - {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"}, -] - [[package]] name = "multidict" version = "6.1.0" @@ -1817,6 +1760,57 @@ files = [ {file = "orjson-3.10.15.tar.gz", hash = "sha256:05ca7fe452a2e9d8d9d706a2984c95b9c2ebc5db417ce0b7a49b91d50642a23e"}, ] +[[package]] +name = "ormsgpack" +version = "1.9.1" +description = "Fast, correct Python msgpack library supporting dataclasses, datetimes, and numpy" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "ormsgpack-1.9.1-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:f1f804fd9c0fd84213a6022c34172f82323b34afa7052a4af18797582cf56365"}, + {file = "ormsgpack-1.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eab5cec99c46276b37071d570aab98603f3d0309b3818da3247eb64bb95e5cfc"}, + {file = "ormsgpack-1.9.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c12c6bb30e6df6fc0213b77f0a5e143f371d618be2e8eb4d555340ce01c6900"}, + {file = "ormsgpack-1.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:994d4bbb7ee333264a3e55e30ccee063df6635d785f21a08bf52f67821454a51"}, + {file = "ormsgpack-1.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a668a584cf4bb6e1a6ef5a35f3f0d0fdae80cfb7237344ad19a50cce8c79317b"}, + {file = "ormsgpack-1.9.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:aaf77699203822638014c604d100f132583844d4fd01eb639a2266970c02cfdf"}, + {file = "ormsgpack-1.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:003d7e1992b447898caf25a820b3037ec68a57864b3e2f34b64693b7d60a9984"}, + {file = "ormsgpack-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:67fefc77e4ba9469f79426769eb4c78acf21f22bef3ab1239a72dd728036ffc2"}, + {file = "ormsgpack-1.9.1-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:16eaf32c33ab4249e242181d59e2509b8e0330d6f65c1d8bf08c3dea38fd7c02"}, + {file = "ormsgpack-1.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c70f2e5b2f9975536e8f7936a9721601dc54febe363d2d82f74c9b31d4fe1c65"}, + {file = "ormsgpack-1.9.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:17c9e18b07d69e3db2e0f8af4731040175e11bdfde78ad8e28126e9e66ec5167"}, + {file = "ormsgpack-1.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73538d749096bb6470328601a2be8f7bdec28849ec6fd19595c232a5848d7124"}, + {file = "ormsgpack-1.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:827ff71de228cfd6d07b9d6b47911aa61b1e8dc995dec3caf8fdcdf4f874bcd0"}, + {file = "ormsgpack-1.9.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:7307f808b3df282c8e8ed92c6ebceeb3eea3d8eeec808438f3f212226b25e217"}, + {file = "ormsgpack-1.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f30aad7fb083bed1c540a3c163c6a9f63a94e3c538860bf8f13386c29b560ad5"}, + {file = "ormsgpack-1.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:829a1b4c5bc3c38ece0c55cf91ebc09c3b987fceb24d3f680c2bcd03fd3789a4"}, + {file = "ormsgpack-1.9.1-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:1ede445fc3fdba219bb0e0d1f289df26a9c7602016b7daac6fafe8fe4e91548f"}, + {file = "ormsgpack-1.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db50b9f918e25b289114312ed775794d0978b469831b992bdc65bfe20b91fe30"}, + {file = "ormsgpack-1.9.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8c7d8fc58e4333308f58ec720b1ee6b12b2b3fe2d2d8f0766ab751cb351e8757"}, + {file = "ormsgpack-1.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeee6d08c040db265cb8563444aba343ecb32cbdbe2414a489dcead9f70c6765"}, + {file = "ormsgpack-1.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2fbb8181c198bdc413a4e889e5200f010724eea4b6d5a9a7eee2df039ac04aca"}, + {file = "ormsgpack-1.9.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:16488f094ac0e2250cceea6caf72962614aa432ee11dd57ef45e1ad25ece3eff"}, + {file = "ormsgpack-1.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:422d960bfd6ad88be20794f50ec7953d8f7a0f2df60e19d0e8feb994e2ed64ee"}, + {file = "ormsgpack-1.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:e6e2f9eab527cf43fb4a4293e493370276b1c8716cf305689202d646c6a782ef"}, + {file = "ormsgpack-1.9.1-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:ac61c18d9dd085e8519b949f7e655f7fb07909fd09c53b4338dd33309012e289"}, + {file = "ormsgpack-1.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134840b8c6615da2c24ce77bd12a46098015c808197a9995c7a2d991e1904eec"}, + {file = "ormsgpack-1.9.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:38fd42618f626394b2c7713c5d4bcbc917254e9753d5d4cde460658b51b11a74"}, + {file = "ormsgpack-1.9.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d36397333ad07b9eba4c2e271fa78951bd81afc059c85a6e9f6c0eb2de07cda"}, + {file = "ormsgpack-1.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:603063089597917d04e4c1b1d53988a34f7dc2ff1a03adcfd1cf4ae966d5fba6"}, + {file = "ormsgpack-1.9.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:94bbf2b185e0cb721ceaba20e64b7158e6caf0cecd140ca29b9f05a8d5e91e2f"}, + {file = "ormsgpack-1.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c38f380b1e8c96a712eb302b9349347385161a8e29046868ae2bfdfcb23e2692"}, + {file = "ormsgpack-1.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:a4bc63fb30db94075611cedbbc3d261dd17cf2aa8ff75a0fd684cd45ca29cb1b"}, + {file = "ormsgpack-1.9.1-cp39-cp39-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e95909248bece8e88a310a913838f17ff5a39190aa4e61de909c3cd27f59744b"}, + {file = "ormsgpack-1.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3939188810c5c641d6b207f29994142ae2b1c70534f7839bbd972d857ac2072"}, + {file = "ormsgpack-1.9.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b6476344a585aea00a2acc9fd07355bf2daac04062cfdd480fa83ec3e2403b"}, + {file = "ormsgpack-1.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7d8b9d53da82b31662ce5a3834b65479cf794a34befb9fc50baa51518383250"}, + {file = "ormsgpack-1.9.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3933d4b0c0d404ee234dbc372836d6f2d2f4b6330c2a2fb9709ba4eaebfae7ba"}, + {file = "ormsgpack-1.9.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:f824e94a7969f0aee9a6847ec232cf731a03b8734951c2a774dd4762308ea2d2"}, + {file = "ormsgpack-1.9.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c1f3f2295374020f9650e4aa7af6403ff016a0d92778b4a48bb3901fd801232d"}, + {file = "ormsgpack-1.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:92eb1b4f7b168da47f547329b4b58d16d8f19508a97ce5266567385d42d81968"}, + {file = "ormsgpack-1.9.1.tar.gz", hash = "sha256:3da6e63d82565e590b98178545e64f0f8506137b92bd31a2d04fd7c82baf5794"}, +] + [[package]] name = "packaging" version = "24.2" @@ -2117,6 +2111,18 @@ azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0 toml = ["tomli (>=2.0.1)"] yaml = ["pyyaml (>=6.0.1)"] +[[package]] +name = "pyflakes" +version = "3.3.2" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a"}, + {file = "pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b"}, +] + [[package]] name = "pygments" version = "2.19.1" @@ -2643,6 +2649,49 @@ files = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] +[[package]] +name = "tomli" +version = "2.2.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version < \"3.11\"" +files = [ + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, +] + [[package]] name = "tqdm" version = "4.67.1" @@ -2834,6 +2883,139 @@ files = [ [package.dependencies] anyio = ">=3.0.0" +[[package]] +name = "xxhash" +version = "3.5.0" +description = "Python binding for xxHash" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "xxhash-3.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ece616532c499ee9afbb83078b1b952beffef121d989841f7f4b3dc5ac0fd212"}, + {file = "xxhash-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3171f693dbc2cef6477054a665dc255d996646b4023fe56cb4db80e26f4cc520"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5d3e570ef46adaf93fc81b44aca6002b5a4d8ca11bd0580c07eac537f36680"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cb29a034301e2982df8b1fe6328a84f4b676106a13e9135a0d7e0c3e9f806da"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0d307d27099bb0cbeea7260eb39ed4fdb99c5542e21e94bb6fd29e49c57a23"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0342aafd421795d740e514bc9858ebddfc705a75a8c5046ac56d85fe97bf196"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dbbd9892c5ebffeca1ed620cf0ade13eb55a0d8c84e0751a6653adc6ac40d0c"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4cc2d67fdb4d057730c75a64c5923abfa17775ae234a71b0200346bfb0a7f482"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ec28adb204b759306a3d64358a5e5c07d7b1dd0ccbce04aa76cb9377b7b70296"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1328f6d8cca2b86acb14104e381225a3d7b42c92c4b86ceae814e5c400dbb415"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8d47ebd9f5d9607fd039c1fbf4994e3b071ea23eff42f4ecef246ab2b7334198"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b96d559e0fcddd3343c510a0fe2b127fbff16bf346dd76280b82292567523442"}, + {file = "xxhash-3.5.0-cp310-cp310-win32.whl", hash = "sha256:61c722ed8d49ac9bc26c7071eeaa1f6ff24053d553146d5df031802deffd03da"}, + {file = "xxhash-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9bed5144c6923cc902cd14bb8963f2d5e034def4486ab0bbe1f58f03f042f9a9"}, + {file = "xxhash-3.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:893074d651cf25c1cc14e3bea4fceefd67f2921b1bb8e40fcfeba56820de80c6"}, + {file = "xxhash-3.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02c2e816896dc6f85922ced60097bcf6f008dedfc5073dcba32f9c8dd786f3c1"}, + {file = "xxhash-3.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6027dcd885e21581e46d3c7f682cfb2b870942feeed58a21c29583512c3f09f8"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1308fa542bbdbf2fa85e9e66b1077eea3a88bef38ee8a06270b4298a7a62a166"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28b2fdcee797e1c1961cd3bcd3d545cab22ad202c846235197935e1df2f8ef7"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:924361811732ddad75ff23e90efd9ccfda4f664132feecb90895bade6a1b4623"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89997aa1c4b6a5b1e5b588979d1da048a3c6f15e55c11d117a56b75c84531f5a"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:685c4f4e8c59837de103344eb1c8a3851f670309eb5c361f746805c5471b8c88"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbd2ecfbfee70bc1a4acb7461fa6af7748ec2ab08ac0fa298f281c51518f982c"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25b5a51dc3dfb20a10833c8eee25903fd2e14059e9afcd329c9da20609a307b2"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a8fb786fb754ef6ff8c120cb96629fb518f8eb5a61a16aac3a979a9dbd40a084"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a905ad00ad1e1c34fe4e9d7c1d949ab09c6fa90c919860c1534ff479f40fd12d"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:963be41bcd49f53af6d795f65c0da9b4cc518c0dd9c47145c98f61cb464f4839"}, + {file = "xxhash-3.5.0-cp311-cp311-win32.whl", hash = "sha256:109b436096d0a2dd039c355fa3414160ec4d843dfecc64a14077332a00aeb7da"}, + {file = "xxhash-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:b702f806693201ad6c0a05ddbbe4c8f359626d0b3305f766077d51388a6bac58"}, + {file = "xxhash-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:c4dcb4120d0cc3cc448624147dba64e9021b278c63e34a38789b688fd0da9bf3"}, + {file = "xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00"}, + {file = "xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e"}, + {file = "xxhash-3.5.0-cp312-cp312-win32.whl", hash = "sha256:f7b58d1fd3551b8c80a971199543379be1cee3d0d409e1f6d8b01c1a2eebf1f8"}, + {file = "xxhash-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0cafd3a2af231b4e113fba24a65d7922af91aeb23774a8b78228e6cd785e3e"}, + {file = "xxhash-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:586886c7e89cb9828bcd8a5686b12e161368e0064d040e225e72607b43858ba2"}, + {file = "xxhash-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37889a0d13b0b7d739cfc128b1c902f04e32de17b33d74b637ad42f1c55101f6"}, + {file = "xxhash-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97a662338797c660178e682f3bc180277b9569a59abfb5925e8620fba00b9fc5"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f85e0108d51092bdda90672476c7d909c04ada6923c14ff9d913c4f7dc8a3bc"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2fd827b0ba763ac919440042302315c564fdb797294d86e8cdd4578e3bc7f3"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82085c2abec437abebf457c1d12fccb30cc8b3774a0814872511f0f0562c768c"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07fda5de378626e502b42b311b049848c2ef38784d0d67b6f30bb5008642f8eb"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c279f0d2b34ef15f922b77966640ade58b4ccdfef1c4d94b20f2a364617a493f"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89e66ceed67b213dec5a773e2f7a9e8c58f64daeb38c7859d8815d2c89f39ad7"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bcd51708a633410737111e998ceb3b45d3dbc98c0931f743d9bb0a209033a326"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ff2c0a34eae7df88c868be53a8dd56fbdf592109e21d4bfa092a27b0bf4a7bf"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e28503dccc7d32e0b9817aa0cbfc1f45f563b2c995b7a66c4c8a0d232e840c7"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a6c50017518329ed65a9e4829154626f008916d36295b6a3ba336e2458824c8c"}, + {file = "xxhash-3.5.0-cp313-cp313-win32.whl", hash = "sha256:53a068fe70301ec30d868ece566ac90d873e3bb059cf83c32e76012c889b8637"}, + {file = "xxhash-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:80babcc30e7a1a484eab952d76a4f4673ff601f54d5142c26826502740e70b43"}, + {file = "xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b"}, + {file = "xxhash-3.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6e5f70f6dca1d3b09bccb7daf4e087075ff776e3da9ac870f86ca316736bb4aa"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e76e83efc7b443052dd1e585a76201e40b3411fe3da7af4fe434ec51b2f163b"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33eac61d0796ca0591f94548dcfe37bb193671e0c9bcf065789b5792f2eda644"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ec70a89be933ea49222fafc3999987d7899fc676f688dd12252509434636622"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86b8e7f703ec6ff4f351cfdb9f428955859537125904aa8c963604f2e9d3e7"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0adfbd36003d9f86c8c97110039f7539b379f28656a04097e7434d3eaf9aa131"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:63107013578c8a730419adc05608756c3fa640bdc6abe806c3123a49fb829f43"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:683b94dbd1ca67557850b86423318a2e323511648f9f3f7b1840408a02b9a48c"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5d2a01dcce81789cf4b12d478b5464632204f4c834dc2d064902ee27d2d1f0ee"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:a9d360a792cbcce2fe7b66b8d51274ec297c53cbc423401480e53b26161a290d"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:f0b48edbebea1b7421a9c687c304f7b44d0677c46498a046079d445454504737"}, + {file = "xxhash-3.5.0-cp37-cp37m-win32.whl", hash = "sha256:7ccb800c9418e438b44b060a32adeb8393764da7441eb52aa2aa195448935306"}, + {file = "xxhash-3.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c3bc7bf8cb8806f8d1c9bf149c18708cb1c406520097d6b0a73977460ea03602"}, + {file = "xxhash-3.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:74752ecaa544657d88b1d1c94ae68031e364a4d47005a90288f3bab3da3c970f"}, + {file = "xxhash-3.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dee1316133c9b463aa81aca676bc506d3f80d8f65aeb0bba2b78d0b30c51d7bd"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:602d339548d35a8579c6b013339fb34aee2df9b4e105f985443d2860e4d7ffaa"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:695735deeddfb35da1677dbc16a083445360e37ff46d8ac5c6fcd64917ff9ade"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1030a39ba01b0c519b1a82f80e8802630d16ab95dc3f2b2386a0b5c8ed5cbb10"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5bc08f33c4966f4eb6590d6ff3ceae76151ad744576b5fc6c4ba8edd459fdec"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160e0c19ee500482ddfb5d5570a0415f565d8ae2b3fd69c5dcfce8a58107b1c3"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f1abffa122452481a61c3551ab3c89d72238e279e517705b8b03847b1d93d738"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:d5e9db7ef3ecbfc0b4733579cea45713a76852b002cf605420b12ef3ef1ec148"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:23241ff6423378a731d84864bf923a41649dc67b144debd1077f02e6249a0d54"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:82b833d5563fefd6fceafb1aed2f3f3ebe19f84760fdd289f8b926731c2e6e91"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0a80ad0ffd78bef9509eee27b4a29e56f5414b87fb01a888353e3d5bda7038bd"}, + {file = "xxhash-3.5.0-cp38-cp38-win32.whl", hash = "sha256:50ac2184ffb1b999e11e27c7e3e70cc1139047e7ebc1aa95ed12f4269abe98d4"}, + {file = "xxhash-3.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:392f52ebbb932db566973693de48f15ce787cabd15cf6334e855ed22ea0be5b3"}, + {file = "xxhash-3.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfc8cdd7f33d57f0468b0614ae634cc38ab9202c6957a60e31d285a71ebe0301"}, + {file = "xxhash-3.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0c48b6300cd0b0106bf49169c3e0536408dfbeb1ccb53180068a18b03c662ab"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe1a92cfbaa0a1253e339ccec42dbe6db262615e52df591b68726ab10338003f"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33513d6cc3ed3b559134fb307aae9bdd94d7e7c02907b37896a6c45ff9ce51bd"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eefc37f6138f522e771ac6db71a6d4838ec7933939676f3753eafd7d3f4c40bc"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a606c8070ada8aa2a88e181773fa1ef17ba65ce5dd168b9d08038e2a61b33754"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42eca420c8fa072cc1dd62597635d140e78e384a79bb4944f825fbef8bfeeef6"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:604253b2143e13218ff1ef0b59ce67f18b8bd1c4205d2ffda22b09b426386898"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6e93a5ad22f434d7876665444a97e713a8f60b5b1a3521e8df11b98309bff833"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:7a46e1d6d2817ba8024de44c4fd79913a90e5f7265434cef97026215b7d30df6"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:30eb2efe6503c379b7ab99c81ba4a779748e3830241f032ab46bd182bf5873af"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c8aa771ff2c13dd9cda8166d685d7333d389fae30a4d2bb39d63ab5775de8606"}, + {file = "xxhash-3.5.0-cp39-cp39-win32.whl", hash = "sha256:5ed9ebc46f24cf91034544b26b131241b699edbfc99ec5e7f8f3d02d6eb7fba4"}, + {file = "xxhash-3.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:220f3f896c6b8d0316f63f16c077d52c412619e475f9372333474ee15133a558"}, + {file = "xxhash-3.5.0-cp39-cp39-win_arm64.whl", hash = "sha256:a7b1d8315d9b5e9f89eb2933b73afae6ec9597a258d52190944437158b49d38e"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2014c5b3ff15e64feecb6b713af12093f75b7926049e26a580e94dcad3c73d8c"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fab81ef75003eda96239a23eda4e4543cedc22e34c373edcaf744e721a163986"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e2febf914ace002132aa09169cc572e0d8959d0f305f93d5828c4836f9bc5a6"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d3a10609c51da2a1c0ea0293fc3968ca0a18bd73838455b5bca3069d7f8e32b"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a74f23335b9689b66eb6dbe2a931a88fcd7a4c2cc4b1cb0edba8ce381c7a1da"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2b4154c00eb22e4d543f472cfca430e7962a0f1d0f3778334f2e08a7ba59363c"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d30bbc1644f726b825b3278764240f449d75f1a8bdda892e641d4a688b1494ae"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fa0b72f2423e2aa53077e54a61c28e181d23effeaafd73fcb9c494e60930c8e"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13de2b76c1835399b2e419a296d5b38dc4855385d9e96916299170085ef72f57"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:0691bfcc4f9c656bcb96cc5db94b4d75980b9d5589f2e59de790091028580837"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:297595fe6138d4da2c8ce9e72a04d73e58725bb60f3a19048bc96ab2ff31c692"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc1276d369452040cbb943300dc8abeedab14245ea44056a2943183822513a18"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2061188a1ba352fc699c82bff722f4baacb4b4b8b2f0c745d2001e56d0dfb514"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38c384c434021e4f62b8d9ba0bc9467e14d394893077e2c66d826243025e1f81"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e6a4dd644d72ab316b580a1c120b375890e4c52ec392d4aef3c63361ec4d77d1"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:531af8845aaadcadf951b7e0c1345c6b9c68a990eeb74ff9acd8501a0ad6a1c9"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ce379bcaa9fcc00f19affa7773084dd09f5b59947b3fb47a1ceb0179f91aaa1"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd1b2281d01723f076df3c8188f43f2472248a6b63118b036e641243656b1b0f"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c770750cc80e8694492244bca7251385188bc5597b6a39d98a9f30e8da984e0"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b150b8467852e1bd844387459aa6fbe11d7f38b56e901f9f3b3e6aba0d660240"}, + {file = "xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f"}, +] + [[package]] name = "yarl" version = "1.18.3" @@ -3047,4 +3229,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.1" python-versions = ">=3.10,<3.13" -content-hash = "01f3e95cb4afe93832bd0ec361abf3d871f87c1e71eac2eec72c631e9085f342" +content-hash = "a89fd4f8d67555fff2b6818ed31dba0f53b298f20853f707e31c02c5cd6ef03c" diff --git a/agent/pyproject.toml b/agent/pyproject.toml index b07c377..b810e5d 100644 --- a/agent/pyproject.toml +++ b/agent/pyproject.toml @@ -4,11 +4,14 @@ version = "0.1.0" description = "Starter" authors = ["Markus Ecker "] license = "MIT" -packages = [{include = "mcp-agent"}] +packages = [{include = "mcp_agent"}] + +[tool.poetry.group.dev.dependencies] +autoflake = "^2.3.1" [project] name = "mcp-agent" -version = "0.0.1" +version = "0.1.0" dependencies = [ "langchain-openai>=0.2.1", "langchain-anthropic>=0.2.1", @@ -22,7 +25,8 @@ dependencies = [ "langgraph-cli[inmem]>=0.1.64", "langchain-mcp-adapters>=0.0.3", "fastmcp>=0.4.1", - "langgraph>=0.3.5" + "langgraph (>=0.3.31,<0.4.0)", + "langgraph-checkpoint (>=2.0.24,<3.0.0)" ] [build-system] @@ -44,6 +48,11 @@ langgraph-cli = {extras = ["inmem"], version = "^0.1.64"} langchain-mcp-adapters = "^0.0.3" fastmcp = "^0.4.1" langgraph = "^0.3.5" +litellm = "^1.43.12" +mem0ai = "^0.1.17" +pydantic = "^2.8.2" +logfire = "^0.19.0" [tool.poetry.scripts] -demo = "mcp-agent.demo:main" +demo = "mcp_agent.run_server:main" +custom-server = "mcp_agent.custom_server_entry:run_custom_server" diff --git a/agent/test_server.py b/agent/test_server.py new file mode 100644 index 0000000..7cdc8c6 --- /dev/null +++ b/agent/test_server.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +""" +Test script to verify the MCP Agent server is working correctly. +This script tests the health endpoint and other basic functionality. +""" + +import sys +import time +import requests +import argparse +import logging + +# Configure logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + +def test_health_endpoint(base_url="http://localhost:8124"): + """Test the health endpoint.""" + try: + logger.info(f"Testing health endpoint at {base_url}/health") + response = requests.get(f"{base_url}/health", timeout=5) + response.raise_for_status() # Raise an exception for 4XX/5XX responses + + logger.info(f"Health endpoint response: {response.status_code} {response.reason}") + logger.info(f"Response body: {response.json()}") + + return True + except requests.exceptions.RequestException as e: + logger.error(f"Error testing health endpoint: {e}") + return False + +def test_routes_endpoint(base_url="http://localhost:8124"): + """Test the routes endpoint.""" + try: + logger.info(f"Testing routes endpoint at {base_url}/routes") + response = requests.get(f"{base_url}/routes", timeout=5) + response.raise_for_status() + + logger.info(f"Routes endpoint response: {response.status_code} {response.reason}") + routes = response.json().get("routes", []) + logger.info(f"Available routes: {len(routes)}") + for route in routes: + logger.info(f" {route['path']} [{','.join(route['methods'])}]") + + return True + except requests.exceptions.RequestException as e: + logger.error(f"Error testing routes endpoint: {e}") + return False + +def test_root_endpoint(base_url="http://localhost:8124"): + """Test the root endpoint.""" + try: + logger.info(f"Testing root endpoint at {base_url}/") + response = requests.get(f"{base_url}/", timeout=5) + response.raise_for_status() + + logger.info(f"Root endpoint response: {response.status_code} {response.reason}") + logger.info(f"Response body: {response.json()}") + + return True + except requests.exceptions.RequestException as e: + logger.error(f"Error testing root endpoint: {e}") + return False + +def main(): + """Main function.""" + parser = argparse.ArgumentParser(description="Test the MCP Agent server") + parser.add_argument("--url", default="http://localhost:8124", help="Base URL of the server") + parser.add_argument("--wait", type=int, default=0, help="Wait time in seconds before testing") + args = parser.parse_args() + + if args.wait > 0: + logger.info(f"Waiting {args.wait} seconds for server to start...") + time.sleep(args.wait) + + # Run the tests + health_ok = test_health_endpoint(args.url) + routes_ok = test_routes_endpoint(args.url) + root_ok = test_root_endpoint(args.url) + + # Print summary + logger.info("\nTest Summary:") + logger.info(f"Health Endpoint: {'✅ PASS' if health_ok else '❌ FAIL'}") + logger.info(f"Routes Endpoint: {'✅ PASS' if routes_ok else '❌ FAIL'}") + logger.info(f"Root Endpoint: {'✅ PASS' if root_ok else '❌ FAIL'}") + + # Exit with appropriate status code + if health_ok and routes_ok and root_ok: + logger.info("All tests passed! Server is working correctly.") + return 0 + else: + logger.error("Some tests failed. Server may not be working correctly.") + return 1 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/check-health-fixed.ps1 b/check-health-fixed.ps1 new file mode 100644 index 0000000..e0a3339 --- /dev/null +++ b/check-health-fixed.ps1 @@ -0,0 +1,175 @@ +# PowerShell script for checking the health of Multi-Agent Canvas services +# This script verifies that both frontend and backend are running and correctly integrated + +# Function to display colored text +function Write-ColorOutput { + param ( + [Parameter(Mandatory=$true)] + [string]$Text, + + [Parameter(Mandatory=$true)] + [string]$Color + ) + + $originalColor = $host.UI.RawUI.ForegroundColor + $host.UI.RawUI.ForegroundColor = $Color + Write-Output $Text + $host.UI.RawUI.ForegroundColor = $originalColor +} + +# Function to check if a URL is accessible +function Test-UrlAccessible { + param ( + [Parameter(Mandatory=$true)] + [string]$Url, + + [int]$TimeoutSeconds = 5 + ) + + try { + $request = [System.Net.WebRequest]::Create($Url) + $request.Timeout = $TimeoutSeconds * 1000 + $response = $request.GetResponse() + $statusCode = [int]($response.StatusCode) + $response.Close() + return @{ + Success = $true + StatusCode = $statusCode + } + } + catch [System.Net.WebException] { + if ($_.Exception.Response -ne $null) { + return @{ + Success = $false + StatusCode = [int]($_.Exception.Response.StatusCode) + Error = $_.Exception.Message + } + } + else { + return @{ + Success = $false + StatusCode = 0 + Error = $_.Exception.Message + } + } + } + catch { + return @{ + Success = $false + StatusCode = 0 + Error = $_.Exception.Message + } + } +} + +# Set script directory as base path +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path + +# Display header +Write-ColorOutput "Multi-Agent Canvas Health Check" "Green" +Write-ColorOutput "===============================" "Green" +Write-Output "" + +# Check backend health +Write-ColorOutput "Checking backend (MCP Agent)..." "Yellow" +$backendHealthUrl = "http://localhost:8123/health" +$backendApiUrl = "http://localhost:8123" +$backendDocsUrl = "http://localhost:8123/docs" + +$backendHealthResult = Test-UrlAccessible -Url $backendHealthUrl +$backendApiResult = Test-UrlAccessible -Url $backendApiUrl +$backendDocsResult = Test-UrlAccessible -Url $backendDocsUrl + +if ($backendHealthResult.Success) { + Write-ColorOutput "✓ Backend health endpoint is accessible (Status: $($backendHealthResult.StatusCode))" "Green" +} +else { + Write-ColorOutput "✗ Backend health endpoint is not accessible: $($backendHealthResult.Error)" "Red" +} + +if ($backendApiResult.Success) { + Write-ColorOutput "✓ Backend API is accessible (Status: $($backendApiResult.StatusCode))" "Green" +} +else { + Write-ColorOutput "✗ Backend API is not accessible: $($backendApiResult.Error)" "Red" +} + +if ($backendDocsResult.Success) { + Write-ColorOutput "✓ Backend API documentation is accessible (Status: $($backendDocsResult.StatusCode))" "Green" +} +else { + Write-ColorOutput "✗ Backend API documentation is not accessible: $($backendDocsResult.Error)" "Red" +} + +# Check frontend +Write-ColorOutput "Checking frontend (Next.js)..." "Yellow" +$frontendUrl = "http://localhost:3000" +$frontendResult = Test-UrlAccessible -Url $frontendUrl + +if ($frontendResult.Success) { + Write-ColorOutput "✓ Frontend is accessible (Status: $($frontendResult.StatusCode))" "Green" +} +else { + Write-ColorOutput "✗ Frontend is not accessible: $($frontendResult.Error)" "Red" +} + +# Check frontend configuration +Write-ColorOutput "Checking frontend configuration..." "Yellow" +$envPath = "$scriptDir\frontend\.env" + +if (Test-Path $envPath) { + $envContent = Get-Content -Path $envPath -ErrorAction SilentlyContinue + + if ($envContent -match "NEXT_PUBLIC_BACKEND_URL=http://localhost:8123") { + Write-ColorOutput "✓ Frontend is correctly configured to connect to the backend" "Green" + } + else { + Write-ColorOutput "✗ Frontend .env file does not contain the correct backend URL" "Red" + Write-ColorOutput " Please ensure NEXT_PUBLIC_BACKEND_URL=http://localhost:8123 is in the frontend/.env file" "Yellow" + } +} +else { + Write-ColorOutput "✗ Frontend .env file not found" "Red" + Write-ColorOutput " Please create a .env file in the frontend directory with NEXT_PUBLIC_BACKEND_URL=http://localhost:8123" "Yellow" +} + +# Check running processes +Write-ColorOutput "Checking running processes..." "Yellow" + +$nodeProcesses = Get-Process -Name "node" -ErrorAction SilentlyContinue +if ($nodeProcesses) { + Write-ColorOutput "✓ Node.js processes are running (Count: $($nodeProcesses.Count))" "Green" +} +else { + Write-ColorOutput "✗ No Node.js processes found - frontend might not be running" "Red" +} + +$pythonProcesses = Get-Process -Name "python" -ErrorAction SilentlyContinue +if ($pythonProcesses) { + Write-ColorOutput "✓ Python processes are running (Count: $($pythonProcesses.Count))" "Green" +} +else { + Write-ColorOutput "✗ No Python processes found - backend might not be running" "Red" +} + +# Summary +Write-Output "" +if ($backendHealthResult.Success -and $frontendResult.Success) { + Write-ColorOutput "✓ Multi-Agent Canvas is running correctly!" "Green" + Write-Output "" + Write-ColorOutput "You can access the application at:" "Cyan" + Write-ColorOutput " Frontend: http://localhost:3000" "Cyan" + Write-ColorOutput " Backend API: http://localhost:8123" "Cyan" + Write-ColorOutput " Backend Health: http://localhost:8123/health" "Cyan" + Write-ColorOutput " API Documentation: http://localhost:8123/docs" "Cyan" +} +else { + Write-ColorOutput "✗ Multi-Agent Canvas is not running correctly" "Red" + Write-Output "" + Write-ColorOutput "Please check the error messages above and try restarting the application." "Yellow" + Write-ColorOutput "You can use start-all.ps1 to restart both services." "Yellow" +} + +Write-Output "" +Write-Output "Press Enter to exit." +Read-Host diff --git a/check-health.bat b/check-health.bat new file mode 100644 index 0000000..3f49dec --- /dev/null +++ b/check-health.bat @@ -0,0 +1,96 @@ +@echo off +REM Script to check the health of Multi-Agent Canvas services +setlocal enabledelayedexpansion + +REM Set colors for better visibility +set "GREEN=[92m" +set "YELLOW=[93m" +set "RED=[91m" +set "CYAN=[96m" +set "RESET=[0m" + +echo %GREEN%Multi-Agent Canvas Health Check%RESET% +echo %GREEN%===============================%RESET% +echo. + +REM Check backend health +echo %YELLOW%Checking backend (MCP Agent)...%RESET% +curl -s -o nul -w "%%{http_code}" http://localhost:8123/health > temp.txt +set /p HEALTH_STATUS= temp.txt +set /p API_STATUS= temp.txt +set /p FRONTEND_STATUS=nul 2>&1 +if %ERRORLEVEL% equ 0 ( + echo %GREEN%✓ Frontend is correctly configured to connect to the backend%RESET% +) else ( + echo %RED%✗ Frontend .env file does not contain the correct backend URL%RESET% + echo %YELLOW% Please ensure NEXT_PUBLIC_BACKEND_URL=http://localhost:8123 is in the frontend/.env file%RESET% +) + +REM Check running processes +echo %YELLOW%Checking running processes...%RESET% +tasklist /FI "IMAGENAME eq node.exe" 2>NUL | find /I "node.exe" >NUL +if %ERRORLEVEL% equ 0 ( + echo %GREEN%✓ Node.js processes are running%RESET% +) else ( + echo %RED%✗ No Node.js processes found - frontend might not be running%RESET% +) + +tasklist /FI "IMAGENAME eq python.exe" 2>NUL | find /I "python.exe" >NUL +if %ERRORLEVEL% equ 0 ( + echo %GREEN%✓ Python processes are running%RESET% +) else ( + echo %RED%✗ No Python processes found - backend might not be running%RESET% +) + +REM Summary +echo. +if "%HEALTH_STATUS%"=="200" if "%FRONTEND_STATUS%"=="200" ( + echo %GREEN%✓ Multi-Agent Canvas is running correctly!%RESET% + echo. + echo %CYAN%You can access the application at:%RESET% + echo %CYAN% Frontend: http://localhost:3000%RESET% + echo %CYAN% Backend API: http://localhost:8123%RESET% + echo %CYAN% Backend Health: http://localhost:8123/health%RESET% + echo %CYAN% API Documentation: http://localhost:8123/docs%RESET% +) else ( + echo %RED%✗ Multi-Agent Canvas is not running correctly%RESET% + echo. + echo %YELLOW%Please check the error messages above and try restarting the application.%RESET% + echo %YELLOW%You can use start-all.bat to restart both services.%RESET% +) + +echo. +echo Press any key to exit. +pause >nul diff --git a/check-health.ps1 b/check-health.ps1 new file mode 100644 index 0000000..e0a3339 --- /dev/null +++ b/check-health.ps1 @@ -0,0 +1,175 @@ +# PowerShell script for checking the health of Multi-Agent Canvas services +# This script verifies that both frontend and backend are running and correctly integrated + +# Function to display colored text +function Write-ColorOutput { + param ( + [Parameter(Mandatory=$true)] + [string]$Text, + + [Parameter(Mandatory=$true)] + [string]$Color + ) + + $originalColor = $host.UI.RawUI.ForegroundColor + $host.UI.RawUI.ForegroundColor = $Color + Write-Output $Text + $host.UI.RawUI.ForegroundColor = $originalColor +} + +# Function to check if a URL is accessible +function Test-UrlAccessible { + param ( + [Parameter(Mandatory=$true)] + [string]$Url, + + [int]$TimeoutSeconds = 5 + ) + + try { + $request = [System.Net.WebRequest]::Create($Url) + $request.Timeout = $TimeoutSeconds * 1000 + $response = $request.GetResponse() + $statusCode = [int]($response.StatusCode) + $response.Close() + return @{ + Success = $true + StatusCode = $statusCode + } + } + catch [System.Net.WebException] { + if ($_.Exception.Response -ne $null) { + return @{ + Success = $false + StatusCode = [int]($_.Exception.Response.StatusCode) + Error = $_.Exception.Message + } + } + else { + return @{ + Success = $false + StatusCode = 0 + Error = $_.Exception.Message + } + } + } + catch { + return @{ + Success = $false + StatusCode = 0 + Error = $_.Exception.Message + } + } +} + +# Set script directory as base path +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path + +# Display header +Write-ColorOutput "Multi-Agent Canvas Health Check" "Green" +Write-ColorOutput "===============================" "Green" +Write-Output "" + +# Check backend health +Write-ColorOutput "Checking backend (MCP Agent)..." "Yellow" +$backendHealthUrl = "http://localhost:8123/health" +$backendApiUrl = "http://localhost:8123" +$backendDocsUrl = "http://localhost:8123/docs" + +$backendHealthResult = Test-UrlAccessible -Url $backendHealthUrl +$backendApiResult = Test-UrlAccessible -Url $backendApiUrl +$backendDocsResult = Test-UrlAccessible -Url $backendDocsUrl + +if ($backendHealthResult.Success) { + Write-ColorOutput "✓ Backend health endpoint is accessible (Status: $($backendHealthResult.StatusCode))" "Green" +} +else { + Write-ColorOutput "✗ Backend health endpoint is not accessible: $($backendHealthResult.Error)" "Red" +} + +if ($backendApiResult.Success) { + Write-ColorOutput "✓ Backend API is accessible (Status: $($backendApiResult.StatusCode))" "Green" +} +else { + Write-ColorOutput "✗ Backend API is not accessible: $($backendApiResult.Error)" "Red" +} + +if ($backendDocsResult.Success) { + Write-ColorOutput "✓ Backend API documentation is accessible (Status: $($backendDocsResult.StatusCode))" "Green" +} +else { + Write-ColorOutput "✗ Backend API documentation is not accessible: $($backendDocsResult.Error)" "Red" +} + +# Check frontend +Write-ColorOutput "Checking frontend (Next.js)..." "Yellow" +$frontendUrl = "http://localhost:3000" +$frontendResult = Test-UrlAccessible -Url $frontendUrl + +if ($frontendResult.Success) { + Write-ColorOutput "✓ Frontend is accessible (Status: $($frontendResult.StatusCode))" "Green" +} +else { + Write-ColorOutput "✗ Frontend is not accessible: $($frontendResult.Error)" "Red" +} + +# Check frontend configuration +Write-ColorOutput "Checking frontend configuration..." "Yellow" +$envPath = "$scriptDir\frontend\.env" + +if (Test-Path $envPath) { + $envContent = Get-Content -Path $envPath -ErrorAction SilentlyContinue + + if ($envContent -match "NEXT_PUBLIC_BACKEND_URL=http://localhost:8123") { + Write-ColorOutput "✓ Frontend is correctly configured to connect to the backend" "Green" + } + else { + Write-ColorOutput "✗ Frontend .env file does not contain the correct backend URL" "Red" + Write-ColorOutput " Please ensure NEXT_PUBLIC_BACKEND_URL=http://localhost:8123 is in the frontend/.env file" "Yellow" + } +} +else { + Write-ColorOutput "✗ Frontend .env file not found" "Red" + Write-ColorOutput " Please create a .env file in the frontend directory with NEXT_PUBLIC_BACKEND_URL=http://localhost:8123" "Yellow" +} + +# Check running processes +Write-ColorOutput "Checking running processes..." "Yellow" + +$nodeProcesses = Get-Process -Name "node" -ErrorAction SilentlyContinue +if ($nodeProcesses) { + Write-ColorOutput "✓ Node.js processes are running (Count: $($nodeProcesses.Count))" "Green" +} +else { + Write-ColorOutput "✗ No Node.js processes found - frontend might not be running" "Red" +} + +$pythonProcesses = Get-Process -Name "python" -ErrorAction SilentlyContinue +if ($pythonProcesses) { + Write-ColorOutput "✓ Python processes are running (Count: $($pythonProcesses.Count))" "Green" +} +else { + Write-ColorOutput "✗ No Python processes found - backend might not be running" "Red" +} + +# Summary +Write-Output "" +if ($backendHealthResult.Success -and $frontendResult.Success) { + Write-ColorOutput "✓ Multi-Agent Canvas is running correctly!" "Green" + Write-Output "" + Write-ColorOutput "You can access the application at:" "Cyan" + Write-ColorOutput " Frontend: http://localhost:3000" "Cyan" + Write-ColorOutput " Backend API: http://localhost:8123" "Cyan" + Write-ColorOutput " Backend Health: http://localhost:8123/health" "Cyan" + Write-ColorOutput " API Documentation: http://localhost:8123/docs" "Cyan" +} +else { + Write-ColorOutput "✗ Multi-Agent Canvas is not running correctly" "Red" + Write-Output "" + Write-ColorOutput "Please check the error messages above and try restarting the application." "Yellow" + Write-ColorOutput "You can use start-all.ps1 to restart both services." "Yellow" +} + +Write-Output "" +Write-Output "Press Enter to exit." +Read-Host diff --git a/frontend/README.md b/frontend/README.md index 212b170..90adea0 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -4,18 +4,20 @@ Open Multi-Agent Canvas is an open-source multi-agent chat interface that levera ## Key Features -- **Multi-Agent Chat Interface:** +- **Multi-Agent Chat Interface:** Chat with a range of specialized agents: - **Travel Agent:** Plan trips, create itineraries, and view travel recommendations on an interactive map powered by Leaflet. - **Research Agent:** Conduct research with real-time logs and progress updates. - -- **Real-Time Interactivity:** + - **MCP Agent:** A general-purpose agent capable of handling various tasks through configurable MCP servers. + - **Knowledge Agent:** A specialized agent for visualizing, querying, and managing knowledge graphs. + +- **Real-Time Interactivity:** Enjoy a live chat powered by `@copilotkit/react-ui` that orchestrates dynamic state changes and agent responses. -- **State Management & Agent Coordination:** +- **State Management & Agent Coordination:** Leverages `@copilotkit/react-core` for robust agent state management and smooth integration of travel and research functionalities. -- **Responsive & Modern UI:** +- **Responsive & Modern UI:** Designed with Tailwind CSS to ensure your experience is smooth and adaptive across all devices. ## Technology Stack @@ -28,58 +30,90 @@ Open Multi-Agent Canvas is an open-source multi-agent chat interface that levera ## Setup Instructions -1. **Prerequisites:** - - [Node.js](https://nodejs.org) (LTS version recommended) - - npm or yarn +1. **Prerequisites:** + - [Node.js](https://nodejs.org) (v18 or later) + - [pnpm](https://pnpm.io/installation) (recommended package manager) + - Backend server running (see main README.md for backend setup) -2. **Installation:** +2. **Installation:** ```bash - # Clone the repository - git clone - # Navigate to the frontend directory cd frontend - # Install dependencies - npm install - # or - yarn install + # Install dependencies with pnpm + pnpm install ``` -3. **Running the Development Server:** +3. **Environment Setup:** ```bash - npm run dev - # or - yarn dev + # Copy the example.env file to .env + cp example.env .env + + # Edit .env to add your Copilot Cloud API key and set the backend URL + # NEXT_PUBLIC_COPILOT_CLOUD_API_KEY=your_api_key_here + # NEXT_PUBLIC_BACKEND_URL=http://localhost:8124 + ``` + +4. **Running the Development Server:** + ```bash + pnpm run dev ``` Then, open [http://localhost:3000](http://localhost:3000) in your browser. +5. **Verifying Backend Connection:** + - The frontend will automatically check if the backend is running at startup + - You can verify the connection status in the MCP Agent interface + - If the backend is not running, you'll see a "Backend Disconnected" message + +## Backend Integration + +The frontend integrates with the backend through the following mechanisms: + +1. **Environment Configuration**: + - The `NEXT_PUBLIC_BACKEND_URL` environment variable specifies the backend URL (default: `http://localhost:8124`) + +2. **Health Check**: + - The MCP Agent component performs a health check to verify the backend is running + - It calls the `/health` endpoint on the backend server + +3. **Server-Sent Events (SSE)**: + - The frontend uses SSE to communicate with the backend in real-time + - This allows for streaming responses from the LLM through the backend + +4. **MCP Configuration**: + - The frontend allows configuring custom MCP servers through the MCP Servers panel + - These configurations are stored in localStorage and passed to the backend when making requests + ## Project Structure -- **/src/app:** +- **/src/app:** Contains Next.js page components, layouts, and global styles. -- **/src/components:** - Houses reusable components including agent interfaces (Travel, Research, Chat, Map, Sidebar) and UI elements. +- **/src/components:** + Houses reusable components including agent interfaces (Travel, Research, MCP, Knowledge, Chat, Map, Sidebar) and UI elements. -- **/providers:** +- **/src/providers:** Wraps the global state providers responsible for managing agent states. -- **/lib:** - Contains utility functions and configuration files (like available agents configuration). +- **/src/lib:** + Contains utility functions and configuration files (like available agents configuration and MCP server types). + +- **/src/hooks:** + Custom React hooks for state management and local storage. ## Value Proposition -Open Multi-Agent Canvas simplifies complex tasks by unifying multiple specialized agents in a single, interactive chat interface. Whether you're planning a trip with an interactive map, conducting in-depth research with real-time logs, this application streamlines your workflow and provides focused assistance tailored to each task—all within one platform. +Open Multi-Agent Canvas simplifies complex tasks by unifying multiple specialized agents in a single, interactive chat interface. Whether you're planning a trip with an interactive map, conducting in-depth research with real-time logs, connecting to MCP servers for specialized tasks, or visualizing knowledge graphs, this application streamlines your workflow and provides focused assistance tailored to each task—all within one platform. ## Deployment The easiest way to deploy this project is with [Vercel](https://vercel.com). Build and start your application with: ```bash -npm run build -npm run start +pnpm run build +pnpm run start ``` -Follow Vercel's deployment guide for more details if needed. + +Note that you'll need to deploy the backend separately and update the `NEXT_PUBLIC_BACKEND_URL` environment variable to point to your deployed backend. ## Contributing diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs index c85fb67..49589eb 100644 --- a/frontend/eslint.config.mjs +++ b/frontend/eslint.config.mjs @@ -10,7 +10,118 @@ const compat = new FlatCompat({ }); const eslintConfig = [ + // Ignore patterns (replacing .eslintignore) + { + ignores: [ + // Build output + ".next/**", + "out/**", + "dist/**", + "build/**", + + // Node modules + "node_modules/**", + + // Cache + ".cache/**", + ".eslintcache", + + // Environment variables + ".env*", + "!.env.example", + + // Public assets + "public/**", + + // Generated files + "*.generated.*", + "next-env.d.ts", + + // Config files + "next.config.js", + "next.config.mjs", + "next.config.ts", + "postcss.config.js", + "postcss.config.mjs", + "tailwind.config.js", + "tailwind.config.ts" + ] + }, + + // Base configurations ...compat.extends("next/core-web-vitals", "next/typescript"), + + // TypeScript specific rules + { + files: ["**/*.ts", "**/*.tsx"], + rules: { + // Handle unused variables + "@typescript-eslint/no-unused-vars": ["error", { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + }], + "no-unused-vars": "off", // Turn off the base rule as it can report incorrect errors + + // Enforce consistent type imports + "@typescript-eslint/consistent-type-imports": ["warn", { + "prefer": "type-imports", + "disallowTypeAnnotations": false + }], + + // Prevent React import when using JSX with React 17+ + "react/react-in-jsx-scope": "off", + + // Enforce consistent React component definition + "react/function-component-definition": ["warn", { + "namedComponents": ["function-declaration", "arrow-function"], + "unnamedComponents": "arrow-function" + }], + + // Enforce proper prop types + "react/prop-types": "off", // TypeScript handles this + + // Enforce consistent imports + "import/order": ["warn", { + "groups": [ + "builtin", + "external", + "internal", + "parent", + "sibling", + "index" + ], + "pathGroups": [ + { + "pattern": "react", + "group": "builtin", + "position": "before" + }, + { + "pattern": "@/**", + "group": "internal", + "position": "after" + } + ], + "pathGroupsExcludedImportTypes": ["react"], + "newlines-between": "always", + "alphabetize": { + "order": "asc", + "caseInsensitive": true + } + }] + } + }, + + // React specific rules + { + files: ["**/*.tsx"], + rules: { + // Enforce hook rules + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn" + } + } ]; export default eslintConfig; diff --git a/frontend/example.env b/frontend/example.env index e7137e3..6a52739 100644 --- a/frontend/example.env +++ b/frontend/example.env @@ -1,2 +1,3 @@ NEXT_PUBLIC_COPILOT_CLOUD_API_KEY= +NEXT_PUBLIC_BACKEND_URL=http://localhost:8123 diff --git a/frontend/package.json b/frontend/package.json index 6bc70ca..ae7b4ca 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,7 +6,8 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "lint:fix": "node lint-fix.js" }, "dependencies": { "@copilotkit/react-core": "1.5.18", @@ -25,6 +26,7 @@ "leaflet-defaulticon-compatibility": "^0.1.2", "lucide-react": "^0.474.0", "next": "15.1.6", + "next-themes": "^0.4.6", "react": "^19.0.0", "react-dom": "^19.0.0", "react-leaflet": "^5.0.0", @@ -43,5 +45,6 @@ "postcss": "^8", "tailwindcss": "^3.4.1", "typescript": "^5" - } + }, + "packageManager": "pnpm@10.6.3+sha512.bb45e34d50a9a76e858a95837301bfb6bd6d35aea2c5d52094fa497a467c43f5c440103ce2511e9e0a2f89c3d6071baac3358fc68ac6fb75e2ceb3d2736065e6" } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index eea5049..20aecfa 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -56,6 +56,9 @@ importers: next: specifier: 15.1.6 version: 15.1.6(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: specifier: ^19.0.0 version: 19.0.0 @@ -2468,6 +2471,12 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + next-themes@0.4.6: + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + next@15.1.6: resolution: {integrity: sha512-Hch4wzbaX0vKQtalpXvUiw5sYivBy4cm5rzUKrBnUB/y436LGrvOUqYvlSeNVCWFO/770gDlltR9gqZH62ct4Q==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} @@ -6201,6 +6210,11 @@ snapshots: neo-async@2.6.2: {} + next-themes@0.4.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + next@15.1.6(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: '@next/env': 15.1.6 diff --git a/frontend/src/app/(canvas-pages)/layout.tsx b/frontend/src/app/(canvas-pages)/layout.tsx index d1ed7a6..7f08760 100644 --- a/frontend/src/app/(canvas-pages)/layout.tsx +++ b/frontend/src/app/(canvas-pages)/layout.tsx @@ -1,7 +1,5 @@ +import { EnhancedLayout } from "@/components/enhanced-layout"; + export default function Layout({ children }: { children: React.ReactNode }) { - return ( -
-
{children}
-
- ); + return {children}; } diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index ccbbcbc..593fa4f 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -3,78 +3,108 @@ @tailwind utilities; body { - font-family: Arial, Helvetica, sans-serif; + font-family: 'Inter', sans-serif; } @layer base { :root { + /* Primary brand colors */ + --brand-primary: 220 70% 50%; /* Vibrant blue */ + --brand-secondary: 280 60% 60%; /* Purple */ + --brand-accent: 160 70% 45%; /* Teal */ + --brand-neutral: 220 15% 20%; /* Dark blue-gray */ + + /* UI colors */ --background: 0 0% 100%; - --foreground: 20 14.3% 4.1%; + --foreground: 220 15% 20%; --card: 0 0% 100%; - --card-foreground: 20 14.3% 4.1%; + --card-foreground: 220 15% 20%; --popover: 0 0% 100%; - --popover-foreground: 20 14.3% 4.1%; - --primary: 24 9.8% 10%; - --primary-foreground: 60 9.1% 97.8%; - --secondary: 60 4.8% 95.9%; - --secondary-foreground: 24 9.8% 10%; - --muted: 60 4.8% 95.9%; - --muted-foreground: 25 5.3% 44.7%; - --accent: 60 4.8% 95.9%; - --accent-foreground: 24 9.8% 10%; - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 60 9.1% 97.8%; - --border: 20 5.9% 90%; - --input: 20 5.9% 90%; - --ring: 20 14.3% 4.1%; - --chart-1: 12 76% 61%; - --chart-2: 173 58% 39%; - --chart-3: 197 37% 24%; - --chart-4: 43 74% 66%; - --chart-5: 27 87% 67%; + --popover-foreground: 220 15% 20%; + --primary: 220 70% 50%; + --primary-foreground: 0 0% 100%; + --secondary: 280 60% 60%; + --secondary-foreground: 0 0% 100%; + --muted: 220 15% 95%; + --muted-foreground: 220 15% 40%; + --accent: 160 70% 45%; + --accent-foreground: 0 0% 100%; + --destructive: 0 70% 50%; + --destructive-foreground: 0 0% 100%; + --border: 220 15% 90%; + --input: 220 15% 90%; + --ring: 220 70% 50%; + --chart-1: 220 70% 50%; + --chart-2: 160 70% 45%; + --chart-3: 280 60% 60%; + --chart-4: 340 70% 50%; + --chart-5: 40 80% 55%; --radius: 0.5rem; - --sidebar-background: 0 0% 98%; - --sidebar-foreground: 240 5.3% 26.1%; - --sidebar-primary: 240 5.9% 10%; - --sidebar-primary-foreground: 0 0% 98%; - --sidebar-accent: 240 4.8% 95.9%; - --sidebar-accent-foreground: 240 5.9% 10%; - --sidebar-border: 220 13% 91%; - --sidebar-ring: 217.2 91.2% 59.8%; + + /* Sidebar specific colors */ + --sidebar-background: 220 15% 20%; + --sidebar-foreground: 0 0% 100%; + --sidebar-primary: 220 70% 50%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 160 70% 45%; + --sidebar-accent-foreground: 0 0% 100%; + --sidebar-border: 220 15% 30%; + --sidebar-ring: 220 70% 50%; + + /* Agent specific colors */ + --agent-travel: 200 70% 50%; + --agent-research: 280 60% 60%; + --agent-mcp: 340 70% 50%; + --agent-knowledge: 160 70% 45%; + + /* Animation durations */ + --animation-fast: 150ms; + --animation-normal: 250ms; + --animation-slow: 350ms; + + /* Shadows */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1); + --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15); } + .dark { - --background: 20 14.3% 4.1%; - --foreground: 60 9.1% 97.8%; - --card: 20 14.3% 4.1%; - --card-foreground: 60 9.1% 97.8%; - --popover: 20 14.3% 4.1%; - --popover-foreground: 60 9.1% 97.8%; - --primary: 60 9.1% 97.8%; - --primary-foreground: 24 9.8% 10%; - --secondary: 12 6.5% 15.1%; - --secondary-foreground: 60 9.1% 97.8%; - --muted: 12 6.5% 15.1%; - --muted-foreground: 24 5.4% 63.9%; - --accent: 12 6.5% 15.1%; - --accent-foreground: 60 9.1% 97.8%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 60 9.1% 97.8%; - --border: 12 6.5% 15.1%; - --input: 12 6.5% 15.1%; - --ring: 24 5.7% 82.9%; + /* UI colors for dark mode */ + --background: 220 15% 10%; + --foreground: 0 0% 95%; + --card: 220 15% 15%; + --card-foreground: 0 0% 95%; + --popover: 220 15% 15%; + --popover-foreground: 0 0% 95%; + --primary: 220 70% 50%; + --primary-foreground: 0 0% 100%; + --secondary: 280 60% 60%; + --secondary-foreground: 0 0% 100%; + --muted: 220 15% 20%; + --muted-foreground: 220 15% 70%; + --accent: 160 70% 45%; + --accent-foreground: 0 0% 100%; + --destructive: 0 70% 50%; + --destructive-foreground: 0 0% 100%; + --border: 220 15% 25%; + --input: 220 15% 25%; + --ring: 220 70% 50%; --chart-1: 220 70% 50%; - --chart-2: 160 60% 45%; - --chart-3: 30 80% 55%; - --chart-4: 280 65% 60%; - --chart-5: 340 75% 55%; - --sidebar-background: 240 5.9% 10%; - --sidebar-foreground: 240 4.8% 95.9%; - --sidebar-primary: 224.3 76.3% 48%; + --chart-2: 160 70% 45%; + --chart-3: 280 60% 60%; + --chart-4: 340 70% 50%; + --chart-5: 40 80% 55%; + + /* Sidebar specific colors for dark mode */ + --sidebar-background: 220 15% 15%; + --sidebar-foreground: 0 0% 95%; + --sidebar-primary: 220 70% 50%; --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 240 3.7% 15.9%; - --sidebar-accent-foreground: 240 4.8% 95.9%; - --sidebar-border: 240 3.7% 15.9%; - --sidebar-ring: 217.2 91.2% 59.8%; + --sidebar-accent: 160 70% 45%; + --sidebar-accent-foreground: 0 0% 100%; + --sidebar-border: 220 15% 25%; + --sidebar-ring: 220 70% 50%; } } @@ -86,3 +116,174 @@ body { @apply bg-background text-foreground; } } + +/* Animation keyframes */ +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes fadeInUp { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes fadeInDown { + from { opacity: 0; transform: translateY(-10px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes slideInLeft { + from { transform: translateX(-20px); opacity: 0; } + to { transform: translateX(0); opacity: 1; } +} + +@keyframes slideInRight { + from { transform: translateX(20px); opacity: 0; } + to { transform: translateX(0); opacity: 1; } +} + +@keyframes pulse { + 0% { opacity: 0.6; } + 50% { opacity: 1; } + 100% { opacity: 0.6; } +} + +@keyframes float { + 0% { transform: translateY(0px); } + 50% { transform: translateY(-5px); } + 100% { transform: translateY(0px); } +} + +@keyframes glow { + 0% { box-shadow: 0 0 5px rgba(var(--primary), 0.5); } + 50% { box-shadow: 0 0 20px rgba(var(--primary), 0.8); } + 100% { box-shadow: 0 0 5px rgba(var(--primary), 0.5); } +} + +@keyframes shimmer { + 0% { background-position: -1000px 0; } + 100% { background-position: 1000px 0; } +} + +@keyframes progress { + 0% { transform: translateX(-100%); } + 50% { transform: translateX(100%); } + 100% { transform: translateX(100%); } +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +@keyframes bounce { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-10px); } +} + +/* Utility animation classes */ +.animate-fade-in { + animation: fadeIn var(--animation-normal) ease-in-out; +} + +.animate-fade-in-up { + animation: fadeInUp var(--animation-normal) ease-in-out; +} + +.animate-fade-in-down { + animation: fadeInDown var(--animation-normal) ease-in-out; +} + +.animate-slide-in-left { + animation: slideInLeft var(--animation-normal) ease-in-out; +} + +.animate-slide-in-right { + animation: slideInRight var(--animation-normal) ease-in-out; +} + +.animate-pulse-subtle { + animation: pulse 2s infinite ease-in-out; +} + +.animate-float { + animation: float 3s infinite ease-in-out; +} + +.animate-glow { + animation: glow 2s infinite ease-in-out; +} + +.animate-shimmer { + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); + background-size: 1000px 100%; + animation: shimmer 2s infinite linear; +} + +.animate-spin-slow { + animation: spin 3s linear infinite; +} + +.animate-bounce-subtle { + animation: bounce 2s infinite ease-in-out; +} + +.animate-progress { + animation: progress 2s infinite ease-in-out; +} + +/* Transition utilities */ +.transition-all-fast { + transition: all var(--animation-fast) ease-in-out; +} + +.transition-all-normal { + transition: all var(--animation-normal) ease-in-out; +} + +.transition-all-slow { + transition: all var(--animation-slow) ease-in-out; +} + +.transition-transform-fast { + transition: transform var(--animation-fast) ease-in-out; +} + +.transition-transform-normal { + transition: transform var(--animation-normal) ease-in-out; +} + +.transition-opacity-fast { + transition: opacity var(--animation-fast) ease-in-out; +} + +.transition-opacity-normal { + transition: opacity var(--animation-normal) ease-in-out; +} + +/* Hover effect utilities */ +.hover-scale { + transition: transform var(--animation-fast) ease-in-out; +} + +.hover-scale:hover { + transform: scale(1.05); +} + +.hover-lift { + transition: transform var(--animation-fast) ease-in-out, box-shadow var(--animation-fast) ease-in-out; +} + +.hover-lift:hover { + transform: translateY(-3px); + box-shadow: var(--shadow-md); +} + +.hover-glow { + transition: box-shadow var(--animation-normal) ease-in-out; +} + +.hover-glow:hover { + box-shadow: 0 0 15px rgba(var(--primary), 0.5); +} diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 366c98d..4f4fe13 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import { Inter, JetBrains_Mono } from "next/font/google"; + import "./globals.css"; import Providers from "@/providers/Providers"; @@ -14,8 +15,12 @@ const jetbrainsMono = JetBrains_Mono({ }); export const metadata: Metadata = { - title: "Open Multi-Agent Canvas", - description: "Open Multi-Agent Canvas by CopilotKit", + title: "Multi-Agent Canvas", + description: "A powerful multi-agent chat interface for specialized AI assistants", + icons: { + icon: "/favicon.ico", + apple: "/logo.svg", + }, }; export default function RootLayout({ @@ -26,7 +31,7 @@ export default function RootLayout({ return ( {children} diff --git a/frontend/src/app/settings/layout.tsx b/frontend/src/app/settings/layout.tsx index c4b2cd8..604aaaa 100644 --- a/frontend/src/app/settings/layout.tsx +++ b/frontend/src/app/settings/layout.tsx @@ -1,11 +1,9 @@ +import { EnhancedLayout } from "@/components/enhanced-layout"; + export default function SettingsLayout({ children, }: { children: React.ReactNode; }) { - return ( -
-
{children}
-
- ); + return {children}; } diff --git a/frontend/src/app/settings/page.tsx b/frontend/src/app/settings/page.tsx index aacd42b..1677bf7 100644 --- a/frontend/src/app/settings/page.tsx +++ b/frontend/src/app/settings/page.tsx @@ -1,23 +1,24 @@ "use client"; import { useEffect, useState } from "react"; -import { useSettings } from "@/providers/SettingsProvider"; -import { - Key, - Save, - Moon, - Sun, - Bug, + +import { + Key, + Save, + Moon, + Sun, + Bug, AlertCircle, Eye, EyeOff, ArrowLeft } from "lucide-react"; +import Link from "next/link"; + import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Separator } from "@/components/ui/separator"; import { Checkbox } from "@/components/ui/checkbox"; -import Link from "next/link"; +import { Input } from "@/components/ui/input"; +import { useSettings } from "@/providers/SettingsProvider"; export default function SettingsPage() { const { settings, updateSettings, isLoaded } = useSettings(); @@ -62,7 +63,7 @@ export default function SettingsPage() { const saveSettings = () => { setIsSaving(true); updateSettings(localSettings); - + // Show success message setSaveMessage("Settings saved successfully!"); setTimeout(() => { @@ -113,7 +114,7 @@ export default function SettingsPage() {