Ver Fonte

Use generic types (#3414)

* use generic types post python 3.10

* use newer optional

* a lost list

* fix integration tests
Engel Nyst há 1 ano atrás
pai
commit
f9f96dd429
29 ficheiros alterados com 159 adições e 148 exclusões
  1. 1 2
      evaluation/EDA/game.py
  2. 8 8
      evaluation/mint/datatypes.py
  3. 2 3
      evaluation/mint/env.py
  4. 2 2
      evaluation/mint/run_infer.py
  5. 2 3
      evaluation/mint/tasks/base.py
  6. 2 3
      evaluation/mint/tasks/codegen.py
  7. 4 4
      evaluation/mint/tasks/reasoning.py
  8. 4 4
      evaluation/mint/utils.py
  9. 2 2
      opendevin/controller/agent_controller.py
  10. 1 2
      opendevin/runtime/client/runtime.py
  11. 18 14
      opendevin/runtime/plugins/agent_skills/agentskills.py
  12. 22 12
      opendevin/security/invariant/analyzer.py
  13. 12 10
      opendevin/security/invariant/client.py
  14. 14 11
      opendevin/security/invariant/nodes.py
  15. 2 2
      opendevin/security/invariant/parser.py
  16. 2 4
      opendevin/server/session/agent.py
  17. 1 2
      opendevin/server/session/manager.py
  18. 4 4
      tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython/prompt_001.log
  19. 4 4
      tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython/prompt_002.log
  20. 4 4
      tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython_module/prompt_001.log
  21. 6 6
      tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython_module/prompt_002.log
  22. 6 6
      tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython_module/prompt_003.log
  23. 6 6
      tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython_module/prompt_004.log
  24. 4 4
      tests/integration/mock/eventstream_runtime/CodeActSWEAgent/test_ipython/prompt_001.log
  25. 4 4
      tests/integration/mock/eventstream_runtime/CodeActSWEAgent/test_ipython/prompt_002.log
  26. 4 4
      tests/integration/mock/eventstream_runtime/CodeActSWEAgent/test_ipython_module/prompt_001.log
  27. 6 6
      tests/integration/mock/eventstream_runtime/CodeActSWEAgent/test_ipython_module/prompt_002.log
  28. 6 6
      tests/integration/mock/eventstream_runtime/CodeActSWEAgent/test_ipython_module/prompt_003.log
  29. 6 6
      tests/integration/mock/eventstream_runtime/CodeActSWEAgent/test_ipython_module/prompt_004.log

+ 1 - 2
evaluation/EDA/game.py

@@ -1,6 +1,5 @@
 import logging
 import re
-from typing import Optional
 
 import openai
 import requests.exceptions
@@ -19,7 +18,7 @@ class Q20Game:
         num_turns: int = 20,
         temperature: float = 0.8,
         openai_api: bool = True,
-        openai_api_key: Optional[str] = None,
+        openai_api_key: str | None = None,
         guesser_kargs=None,
     ) -> None:
         if guesser_kargs is None:

+ 8 - 8
evaluation/mint/datatypes.py

@@ -1,5 +1,5 @@
 import enum
-from typing import Any, Dict, Tuple
+from typing import Any
 
 
 class TaskState:
@@ -9,11 +9,11 @@ class TaskState:
         success: bool = False,
         agent_action_count: dict = None,
         terminate_reason: str = None,
-        latest_output: Dict[str, Any] = None,
+        latest_output: dict[str, Any] = None,
     ):
         self.finished = finished
         self.success = success
-        self.agent_action_count: Dict[str, int] = (
+        self.agent_action_count: dict[str, int] = (
             agent_action_count
             if agent_action_count
             else {
@@ -25,7 +25,7 @@ class TaskState:
         self.terminate_reason = terminate_reason
         self.latest_output = latest_output
 
-    def to_dict(self) -> Dict[str, Any]:
+    def to_dict(self) -> dict[str, Any]:
         return {
             'finished': self.finished,
             'success': self.success,
@@ -50,12 +50,12 @@ class StepOutput:
         self,
         observation: str = None,
         success: bool = False,
-        extra: Dict[str, Any] = None,
-        turn_info: Tuple[int, int] = None,
+        extra: dict[str, Any] = None,
+        turn_info: tuple[int, int] = None,
     ):
         self.observation: str = observation
         self.success: bool = success
-        self.extra: Dict[str, Any] = extra
+        self.extra: dict[str, Any] = extra
         self.turn_info = turn_info
 
     def __repr__(self) -> str:
@@ -79,7 +79,7 @@ class StepOutput:
 
         return output
 
-    def to_dict(self) -> Dict[str, Any]:
+    def to_dict(self) -> dict[str, Any]:
         return {
             'observation': self.observation,
             'success': self.success,

+ 2 - 3
evaluation/mint/env.py

@@ -1,6 +1,5 @@
 import re
 import traceback
-from typing import Dict, Optional
 
 from datatypes import ParseError, StepOutput, TaskState
 from tasks.base import Task
@@ -16,7 +15,7 @@ class SimplifiedEnv:
         'For example: The answer to the question is <solution> 42 </solution>. \n'
     )
 
-    def __init__(self, agent_state: State, task: Task, task_config: Dict[str, int]):
+    def __init__(self, agent_state: State, task: Task, task_config: dict[str, int]):
         self.agent_state = agent_state
         self.task = task
 
@@ -58,7 +57,7 @@ class SimplifiedEnv:
         self.log_output(output)
         return self.task_state
 
-    def handle_propose_solution(self, lm_message) -> Optional[str]:
+    def handle_propose_solution(self, lm_message) -> str | None:
         """Propose answer to check the task success.
 
         It might set self.state.finished = True if the task is successful.

+ 2 - 2
evaluation/mint/run_infer.py

@@ -1,6 +1,6 @@
 import functools
 import os
-from typing import Any, Dict
+from typing import Any
 
 import pandas as pd
 from datasets import load_dataset
@@ -33,7 +33,7 @@ from opendevin.events.observation import CmdOutputObservation
 from opendevin.runtime.runtime import Runtime
 
 
-def codeact_user_response_mint(state: State, task: Task, task_config: Dict[str, int]):
+def codeact_user_response_mint(state: State, task: Task, task_config: dict[str, int]):
     logger.info(f'Gold reference: {task.reference}')
     logger.info(f'Task config: {task_config}')
 

+ 2 - 3
evaluation/mint/tasks/base.py

@@ -2,7 +2,6 @@ import json
 import logging
 import os
 from abc import ABC, abstractmethod
-from typing import List, Optional, Tuple
 
 from utils import load_file
 
@@ -58,7 +57,7 @@ class Task(ABC):
         return self._reference
 
     @abstractmethod
-    def extract_answer(self, solution: str) -> Optional[str]:
+    def extract_answer(self, solution: str) -> str | None:
         """Extract the answer from the given solution."""
         pass
 
@@ -72,7 +71,7 @@ class Task(ABC):
         return answer == self.reference
 
     @classmethod
-    def load_tasks(cls, path: str) -> Tuple[List['Task'], int]:
+    def load_tasks(cls, path: str) -> tuple[list['Task'], int]:
         """Load all the tasks from a given jsonl file."""
         assert path.endswith('.jsonl') or path.endswith('.json')
         with open(path, 'r') as f:

+ 2 - 3
evaluation/mint/tasks/codegen.py

@@ -1,5 +1,4 @@
 import logging
-from typing import Optional
 
 from utils import check_correctness
 
@@ -42,7 +41,7 @@ class MBPPTask(CodeGenTask):
         """
         return self._prompt.replace('"""', '').strip()
 
-    def extract_answer(self, solution: str) -> Optional[str]:
+    def extract_answer(self, solution: str) -> str | None:
         """Extract the answer from the given solution.
 
         Split off first block of code by scanning for class, def etc. on newlines.
@@ -66,7 +65,7 @@ class HumanEvalTask(CodeGenTask):
         """
         return 'Complete the following code:\n\n' + self._prompt
 
-    def extract_answer(self, solution: str) -> Optional[str]:
+    def extract_answer(self, solution: str) -> str | None:
         """Extract the answer from the given solution.
 
         Split off first block of code by scanning for class, def etc. on newlines.

+ 4 - 4
evaluation/mint/tasks/reasoning.py

@@ -2,7 +2,7 @@ import ast
 import logging
 import re
 import traceback
-from typing import Any, Optional
+from typing import Any
 
 import numpy as np
 from sympy import Rational
@@ -21,7 +21,7 @@ class ReasoningTask(Task):
         self._prompt = prompt.strip()
         self._reference = str(reference).strip().lower()
 
-    def extract_answer(self, solution: str) -> Optional[str]:
+    def extract_answer(self, solution: str) -> str | None:
         """Extract the answer from the given solution."""
         return solution.lower().strip()
 
@@ -66,7 +66,7 @@ class MultipleChoiceTask(Task):
             pass
         self.metadata.update({'options': self._options})
 
-    def extract_answer(self, solution: str) -> Optional[str]:
+    def extract_answer(self, solution: str) -> str | None:
         # Extract the selected option from the solution
         solution = solution.lower().strip()
         for letter in 'abcdefghijklmnopqrstuvwxyz':
@@ -204,7 +204,7 @@ class TheoremqaTask(Task):
         self._reference = reference
         self._answer_type = kwargs.get('answer_type')
 
-    def extract_answer(self, solution: str) -> Optional[Any]:
+    def extract_answer(self, solution: str) -> Any:
         """Extract the answer from the given solution."""
         prediction = solution
         # Following the preprocessing steps from TheoremQA

+ 4 - 4
evaluation/mint/utils.py

@@ -7,7 +7,7 @@ import os
 import platform
 import signal
 import tempfile
-from typing import Any, Dict, Optional
+from typing import Any
 
 
 # use cache to avoid loading the same file multiple times
@@ -77,8 +77,8 @@ def check_correctness(
     solution_code: str,
     test_code: str,
     timeout: float = 10,
-    completion_id: Optional[int] = None,
-) -> Dict:
+    completion_id: int | None = None,
+) -> dict:
     """Evaluates the functional correctness of a completion by running the test
     suite provided in the problem.
 
@@ -178,7 +178,7 @@ def chdir(root):
         os.chdir(cwd)
 
 
-def reliability_guard(maximum_memory_bytes: Optional[int] = None):
+def reliability_guard(maximum_memory_bytes: int | None = None):
     """This disables various destructive functions and prevents the generated code
     from interfering with the test (e.g. fork bomb, killing other processes,
     removing filesystem files, etc.)

+ 2 - 2
opendevin/controller/agent_controller.py

@@ -1,6 +1,6 @@
 import asyncio
 import traceback
-from typing import Optional, Type
+from typing import Type
 
 from opendevin.controller.agent import Agent
 from opendevin.controller.state.state import State, TrafficControlState
@@ -52,7 +52,7 @@ class AgentController:
     state: State
     confirmation_mode: bool
     agent_to_llm_config: dict[str, LLMConfig]
-    agent_task: Optional[asyncio.Task] = None
+    agent_task: asyncio.Task | None = None
     parent: 'AgentController | None' = None
     delegate: 'AgentController | None' = None
     _pending_action: Action | None = None

+ 1 - 2
opendevin/runtime/client/runtime.py

@@ -2,7 +2,6 @@ import asyncio
 import os
 import tempfile
 import uuid
-from typing import Optional
 from zipfile import ZipFile
 
 import aiohttp
@@ -55,7 +54,7 @@ class EventStreamRuntime(Runtime):
         )  # will initialize the event stream
         self._port = find_available_tcp_port()
         self.api_url = f'http://{self.config.sandbox.api_hostname}:{self._port}'
-        self.session: Optional[aiohttp.ClientSession] = None
+        self.session: aiohttp.ClientSession | None = None
 
         self.instance_id = (
             sid + str(uuid.uuid4()) if sid is not None else str(uuid.uuid4())

+ 18 - 14
opendevin/runtime/plugins/agent_skills/agentskills.py

@@ -22,7 +22,6 @@ import re
 import shutil
 import tempfile
 from inspect import signature
-from typing import Optional
 
 import docx
 import PyPDF2
@@ -121,12 +120,12 @@ def _clamp(value, min_value, max_value):
     return max(min_value, min(value, max_value))
 
 
-def _lint_file(file_path: str) -> tuple[Optional[str], Optional[int]]:
+def _lint_file(file_path: str) -> tuple[str | None, int | None]:
     """Lint the file at the given path and return a tuple with a boolean indicating if there are errors,
     and the line number of the first error, if any.
 
     Returns:
-        tuple[str, Optional[int]]: (lint_error, first_error_line_number)
+        tuple[str | None, int | None]: (lint_error, first_error_line_number)
     """
     linter = Linter(root=os.getcwd())
     lint_error = linter.lint(file_path)
@@ -518,7 +517,7 @@ def _edit_file_impl(
                 f.writelines(lines)
 
             lint_error, first_error_line = _lint_file(file_name)
-            
+
             # Select the errors caused by the modification
             def extract_last_part(line):
                 parts = line.split(':')
@@ -532,13 +531,18 @@ def _edit_file_impl(
 
                 last_parts1 = [extract_last_part(line) for line in lines1]
 
-                remaining_lines = [line for line in lines2 if extract_last_part(line) not in last_parts1]
+                remaining_lines = [
+                    line
+                    for line in lines2
+                    if extract_last_part(line) not in last_parts1
+                ]
 
                 result = '\n'.join(remaining_lines)
                 return result
+
             if original_lint_error and lint_error:
                 lint_error = subtract_strings(original_lint_error, lint_error)
-                if lint_error == "":
+                if lint_error == '':
                     lint_error = None
                     first_error_line = None
 
@@ -784,7 +788,7 @@ def search_dir(search_term: str, dir_path: str = './') -> None:
 
     Args:
         search_term: str: The term to search for.
-        dir_path: Optional[str]: The path to the directory to search.
+        dir_path: str: The path to the directory to search.
     """
     if not os.path.isdir(dir_path):
         raise FileNotFoundError(f'Directory {dir_path} not found')
@@ -818,12 +822,12 @@ def search_dir(search_term: str, dir_path: str = './') -> None:
     print(f'[End of matches for "{search_term}" in {dir_path}]')
 
 
-def search_file(search_term: str, file_path: Optional[str] = None) -> None:
+def search_file(search_term: str, file_path: str | None = None) -> None:
     """Searches for search_term in file. If file is not provided, searches in the current open file.
 
     Args:
         search_term: str: The term to search for.
-        file_path: Optional[str]: The path to the file to search.
+        file_path: str | None: The path to the file to search.
     """
     global CURRENT_FILE
     if file_path is None:
@@ -855,7 +859,7 @@ def find_file(file_name: str, dir_path: str = './') -> None:
 
     Args:
         file_name: str: The name of the file to find.
-        dir_path: Optional[str]: The path to the directory to search.
+        dir_path: str: The path to the directory to search.
     """
     if not os.path.isdir(dir_path):
         raise FileNotFoundError(f'Directory {dir_path} not found')
@@ -964,7 +968,7 @@ def parse_audio(file_path: str, model: str = 'whisper-1') -> None:
 
     Args:
         file_path: str: The path to the audio file to transcribe.
-        model: Optional[str]: The audio model to use for transcription. Defaults to 'whisper-1'.
+        model: str: The audio model to use for transcription. Defaults to 'whisper-1'.
     """
     print(f'[Transcribing audio file from {file_path}]')
     try:
@@ -986,7 +990,7 @@ def parse_image(
 
     Args:
         file_path: str: The path to the file to open.
-        task: Optional[str]: The task description for the API call. Defaults to 'Describe this image as detail as possible.'.
+        task: str: The task description for the API call. Defaults to 'Describe this image as detail as possible.'.
     """
     print(f'[Reading image file from {file_path}]')
     # TODO: record the COST of the API call
@@ -1013,8 +1017,8 @@ def parse_video(
 
     Args:
         file_path: str: The path to the video file to open.
-        task: Optional[str]: The task description for the API call. Defaults to 'Describe this image as detail as possible.'.
-        frame_interval: Optional[int]: The interval between frames to analyze. Defaults to 30.
+        task: str: The task description for the API call. Defaults to 'Describe this image as detail as possible.'.
+        frame_interval: int: The interval between frames to analyze. Defaults to 30.
 
     """
     print(

+ 22 - 12
opendevin/security/invariant/analyzer.py

@@ -1,9 +1,8 @@
+import re
 import uuid
-from typing import Any, Optional, List
+from typing import Any
 
 import docker
-import re
-
 from fastapi import HTTPException, Request
 from fastapi.responses import JSONResponse
 
@@ -36,8 +35,8 @@ class InvariantAnalyzer(SecurityAnalyzer):
     def __init__(
         self,
         event_stream: EventStream,
-        policy: Optional[str] = None,
-        sid: Optional[str] = None,
+        policy: str | None = None,
+        sid: str | None = None,
     ):
         """Initializes a new instance of the InvariantAnalzyer class."""
         super().__init__(event_stream)
@@ -51,7 +50,7 @@ class InvariantAnalyzer(SecurityAnalyzer):
             self.docker_client = docker.from_env()
         except Exception as ex:
             logger.exception(
-                f'Error creating Invariant Security Analyzer container. Please check that Docker is running or disable the Security Analyzer in settings.',
+                'Error creating Invariant Security Analyzer container. Please check that Docker is running or disable the Security Analyzer in settings.',
                 exc_info=False,
             )
             raise ex
@@ -110,8 +109,12 @@ class InvariantAnalyzer(SecurityAnalyzer):
         else:
             logger.info('Invariant skipping element: event')
 
-    def get_risk(self, results: List[str]) -> ActionSecurityRisk:
-        mapping = {"high": ActionSecurityRisk.HIGH, "medium": ActionSecurityRisk.MEDIUM, "low": ActionSecurityRisk.LOW}
+    def get_risk(self, results: list[str]) -> ActionSecurityRisk:
+        mapping = {
+            'high': ActionSecurityRisk.HIGH,
+            'medium': ActionSecurityRisk.MEDIUM,
+            'low': ActionSecurityRisk.LOW,
+        }
         regex = r'(?<=risk=)\w+'
         risks = []
         for result in results:
@@ -130,10 +133,17 @@ class InvariantAnalyzer(SecurityAnalyzer):
 
     async def should_confirm(self, event: Event) -> bool:
         risk = event.security_risk  # type: ignore [attr-defined]
-        return risk is not None and risk < self.settings.get('RISK_SEVERITY', ActionSecurityRisk.MEDIUM) and hasattr(event, 'is_confirmed') and event.is_confirmed == "awaiting_confirmation"
+        return (
+            risk is not None
+            and risk < self.settings.get('RISK_SEVERITY', ActionSecurityRisk.MEDIUM)
+            and hasattr(event, 'is_confirmed')
+            and event.is_confirmed == 'awaiting_confirmation'
+        )
 
     async def confirm(self, event: Event) -> None:
-        new_event = action_from_dict({"action":"change_agent_state", "args":{"agent_state":"user_confirmed"}})
+        new_event = action_from_dict(
+            {'action': 'change_agent_state', 'args': {'agent_state': 'user_confirmed'}}
+        )
         if event.source:
             self.event_stream.add_event(new_event, event.source)
         else:
@@ -172,7 +182,7 @@ class InvariantAnalyzer(SecurityAnalyzer):
                 return await self.update_policy(request)
             elif endpoint == 'settings':
                 return await self.update_settings(request)
-        raise HTTPException(status_code=405, detail="Method Not Allowed")
+        raise HTTPException(status_code=405, detail='Method Not Allowed')
 
     async def export_trace(self, request: Request) -> Any:
         return JSONResponse(content=self.input)
@@ -193,4 +203,4 @@ class InvariantAnalyzer(SecurityAnalyzer):
     async def update_settings(self, request: Request) -> Any:
         settings = await request.json()
         self.settings = settings
-        return JSONResponse(content=self.settings)
+        return JSONResponse(content=self.settings)

+ 12 - 10
opendevin/security/invariant/client.py

@@ -1,5 +1,5 @@
 import time
-from typing import Any, Optional, Tuple, Union, List, Dict
+from typing import Any, Union
 
 import requests
 from requests.exceptions import ConnectionError, HTTPError, Timeout
@@ -8,7 +8,7 @@ from requests.exceptions import ConnectionError, HTTPError, Timeout
 class InvariantClient:
     timeout: int = 120
 
-    def __init__(self, server_url: str, session_id: Optional[str] = None):
+    def __init__(self, server_url: str, session_id: str | None = None):
         self.server = server_url
         self.session_id, err = self._create_session(session_id)
         if err:
@@ -17,8 +17,8 @@ class InvariantClient:
         self.Monitor = self._Monitor(self)
 
     def _create_session(
-        self, session_id: Optional[str] = None
-    ) -> Tuple[Optional[str], Optional[Exception]]:
+        self, session_id: str | None = None
+    ) -> tuple[str | None, Exception | None]:
         elapsed = 0
         while elapsed < self.timeout:
             try:
@@ -54,7 +54,7 @@ class InvariantClient:
             self.server = invariant.server
             self.session_id = invariant.session_id
 
-        def _create_policy(self, rule: str) -> Tuple[Optional[str], Optional[Exception]]:
+        def _create_policy(self, rule: str) -> tuple[str | None, Exception | None]:
             try:
                 response = requests.post(
                     f'{self.server}/policy/new?session_id={self.session_id}',
@@ -66,7 +66,7 @@ class InvariantClient:
             except (ConnectionError, Timeout, HTTPError) as err:
                 return None, err
 
-        def get_template(self) -> Tuple[Optional[str], Optional[Exception]]:
+        def get_template(self) -> tuple[str | None, Exception | None]:
             try:
                 response = requests.get(
                     f'{self.server}/policy/template',
@@ -84,7 +84,7 @@ class InvariantClient:
             self.policy_id = policy_id
             return self
 
-        def analyze(self, trace: List[Dict]) -> Union[Any, Exception]:
+        def analyze(self, trace: list[dict]) -> Union[Any, Exception]:
             try:
                 response = requests.post(
                     f'{self.server}/policy/{self.policy_id}/analyze?session_id={self.session_id}',
@@ -102,7 +102,7 @@ class InvariantClient:
             self.session_id = invariant.session_id
             self.policy = ''
 
-        def _create_monitor(self, rule: str) -> Tuple[Optional[str], Optional[Exception]]:
+        def _create_monitor(self, rule: str) -> tuple[str | None, Exception | None]:
             try:
                 response = requests.post(
                     f'{self.server}/monitor/new?session_id={self.session_id}',
@@ -122,11 +122,13 @@ class InvariantClient:
             self.policy = rule
             return self
 
-        def check(self, past_events: List[Dict], pending_events: List[Dict]) -> Union[Any, Exception]:
+        def check(
+            self, past_events: list[dict], pending_events: list[dict]
+        ) -> Union[Any, Exception]:
             try:
                 response = requests.post(
                     f'{self.server}/monitor/{self.monitor_id}/check?session_id={self.session_id}',
-                    json={"past_events": past_events, "pending_events": pending_events},
+                    json={'past_events': past_events, 'pending_events': pending_events},
                     timeout=60,
                 )
                 response.raise_for_status()

+ 14 - 11
opendevin/security/invariant/nodes.py

@@ -1,14 +1,17 @@
-from pydantic.dataclasses import dataclass
 from pydantic import BaseModel, Field
-from typing import Optional
+from pydantic.dataclasses import dataclass
+
 
 @dataclass
 class LLM:
     vendor: str
     model: str
 
+
 class Event(BaseModel):
-    metadata: Optional[dict] = Field(default_factory=dict, description="Metadata associated with the event")
+    metadata: dict | None = Field(
+        default_factory=dict, description='Metadata associated with the event'
+    )
 
 
 class Function(BaseModel):
@@ -24,19 +27,19 @@ class ToolCall(Event):
 
 class Message(Event):
     role: str
-    content: Optional[str]
-    tool_calls: Optional[list[ToolCall]] = None
-    
+    content: str | None
+    tool_calls: list[ToolCall] | None = None
+
     def __rich_repr__(self):
         # Print on separate line
-        yield "role", self.role
-        yield "content", self.content
-        yield "tool_calls", self.tool_calls
+        yield 'role', self.role
+        yield 'content', self.content
+        yield 'tool_calls', self.tool_calls
 
 
 class ToolOutput(Event):
     role: str
     content: str
-    tool_call_id: Optional[str]
+    tool_call_id: str | None = None
 
-    _tool_call: Optional[ToolCall]
+    _tool_call: ToolCall | None = None

+ 2 - 2
opendevin/security/invariant/parser.py

@@ -1,4 +1,4 @@
-from typing import Optional, Union
+from typing import Union
 
 from pydantic import BaseModel, Field
 
@@ -31,7 +31,7 @@ def get_next_id(trace: list[TraceElement]) -> str:
 
 def get_last_id(
     trace: list[TraceElement],
-) -> Optional[str]:
+) -> str | None:
     for el in reversed(trace):
         if type(el) == ToolCall:
             return el.id

+ 2 - 4
opendevin/server/session/agent.py

@@ -1,5 +1,3 @@
-from typing import Optional
-
 from opendevin.controller import AgentController
 from opendevin.controller.agent import Agent
 from opendevin.controller.state.state import State
@@ -22,8 +20,8 @@ class AgentSession:
     sid: str
     event_stream: EventStream
     file_store: FileStore
-    controller: Optional[AgentController] = None
-    runtime: Optional[Runtime] = None
+    controller: AgentController | None = None
+    runtime: Runtime | None = None
     security_analyzer: SecurityAnalyzer | None = None
     _closed: bool = False
 

+ 1 - 2
opendevin/server/session/manager.py

@@ -1,6 +1,5 @@
 import asyncio
 import time
-from typing import Optional
 
 from fastapi import WebSocket
 
@@ -61,7 +60,7 @@ class SessionManager:
                     session_ids_to_remove.append(sid)
 
             for sid in session_ids_to_remove:
-                to_del_session: Optional[Session] = self._sessions.pop(sid, None)
+                to_del_session: Session | None = self._sessions.pop(sid, None)
                 if to_del_session is not None:
                     await to_del_session.close()
                     logger.info(

+ 4 - 4
tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython/prompt_001.log

@@ -126,19 +126,19 @@ search_dir(search_term: str, dir_path: str = './') -> None:
     Searches for search_term in all files in dir. If dir is not provided, searches in the current directory.
     Args:
     search_term: str: The term to search for.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
-search_file(search_term: str, file_path: Optional[str] = None) -> None:
+search_file(search_term: str, file_path: str | None = None) -> None:
     Searches for search_term in file. If file is not provided, searches in the current open file.
     Args:
     search_term: str: The term to search for.
-    file_path: Optional[str]: The path to the file to search.
+    file_path: str | None: The path to the file to search.
 
 find_file(file_name: str, dir_path: str = './') -> None:
     Finds all files with the given name in the specified directory.
     Args:
     file_name: str: The name of the file to find.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
 parse_pdf(file_path: str) -> None:
     Parses the content of a PDF file and prints it.

+ 4 - 4
tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython/prompt_002.log

@@ -126,19 +126,19 @@ search_dir(search_term: str, dir_path: str = './') -> None:
     Searches for search_term in all files in dir. If dir is not provided, searches in the current directory.
     Args:
     search_term: str: The term to search for.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
-search_file(search_term: str, file_path: Optional[str] = None) -> None:
+search_file(search_term: str, file_path: str | None = None) -> None:
     Searches for search_term in file. If file is not provided, searches in the current open file.
     Args:
     search_term: str: The term to search for.
-    file_path: Optional[str]: The path to the file to search.
+    file_path: str | None: The path to the file to search.
 
 find_file(file_name: str, dir_path: str = './') -> None:
     Finds all files with the given name in the specified directory.
     Args:
     file_name: str: The name of the file to find.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
 parse_pdf(file_path: str) -> None:
     Parses the content of a PDF file and prints it.

+ 4 - 4
tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython_module/prompt_001.log

@@ -126,19 +126,19 @@ search_dir(search_term: str, dir_path: str = './') -> None:
     Searches for search_term in all files in dir. If dir is not provided, searches in the current directory.
     Args:
     search_term: str: The term to search for.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
-search_file(search_term: str, file_path: Optional[str] = None) -> None:
+search_file(search_term: str, file_path: str | None = None) -> None:
     Searches for search_term in file. If file is not provided, searches in the current open file.
     Args:
     search_term: str: The term to search for.
-    file_path: Optional[str]: The path to the file to search.
+    file_path: str | None: The path to the file to search.
 
 find_file(file_name: str, dir_path: str = './') -> None:
     Finds all files with the given name in the specified directory.
     Args:
     file_name: str: The name of the file to find.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
 parse_pdf(file_path: str) -> None:
     Parses the content of a PDF file and prints it.

+ 6 - 6
tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython_module/prompt_002.log

@@ -126,19 +126,19 @@ search_dir(search_term: str, dir_path: str = './') -> None:
     Searches for search_term in all files in dir. If dir is not provided, searches in the current directory.
     Args:
     search_term: str: The term to search for.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
-search_file(search_term: str, file_path: Optional[str] = None) -> None:
+search_file(search_term: str, file_path: str | None = None) -> None:
     Searches for search_term in file. If file is not provided, searches in the current open file.
     Args:
     search_term: str: The term to search for.
-    file_path: Optional[str]: The path to the file to search.
+    file_path: str | None: The path to the file to search.
 
 find_file(file_name: str, dir_path: str = './') -> None:
     Finds all files with the given name in the specified directory.
     Args:
     file_name: str: The name of the file to find.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
 parse_pdf(file_path: str) -> None:
     Parses the content of a PDF file and prints it.
@@ -412,12 +412,12 @@ Sure! Let's start by installing the `pymsgbox` package.
 OBSERVATION:
 Collecting pymsgbox==1.0.9
   Downloading PyMsgBox-1.0.9.tar.gz (18 kB)
-  Installing build dependencies ... [?25l- \ | / - \ | / - \ | / - \ | / - done
+  Installing build dependencies ... [?25l- \ | / - done
 [?25h  Getting requirements to build wheel ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25hBuilding wheels for collected packages: pymsgbox
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
-[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=0aa91e03ed30fdcf42db3522fd0444c80951cccb215529a6ed9a95fc2b8a6f0c
+[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=ef14d567d93427570776c0692e72503e364d52371ced8369846b1c6f72ea9a2b
   Stored in directory: /home/opendevin/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
 Successfully built pymsgbox
 Installing collected packages: pymsgbox

+ 6 - 6
tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython_module/prompt_003.log

@@ -126,19 +126,19 @@ search_dir(search_term: str, dir_path: str = './') -> None:
     Searches for search_term in all files in dir. If dir is not provided, searches in the current directory.
     Args:
     search_term: str: The term to search for.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
-search_file(search_term: str, file_path: Optional[str] = None) -> None:
+search_file(search_term: str, file_path: str | None = None) -> None:
     Searches for search_term in file. If file is not provided, searches in the current open file.
     Args:
     search_term: str: The term to search for.
-    file_path: Optional[str]: The path to the file to search.
+    file_path: str | None: The path to the file to search.
 
 find_file(file_name: str, dir_path: str = './') -> None:
     Finds all files with the given name in the specified directory.
     Args:
     file_name: str: The name of the file to find.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
 parse_pdf(file_path: str) -> None:
     Parses the content of a PDF file and prints it.
@@ -412,12 +412,12 @@ Sure! Let's start by installing the `pymsgbox` package.
 OBSERVATION:
 Collecting pymsgbox==1.0.9
   Downloading PyMsgBox-1.0.9.tar.gz (18 kB)
-  Installing build dependencies ... [?25l- \ | / - \ | / - \ | / - \ | / - done
+  Installing build dependencies ... [?25l- \ | / - done
 [?25h  Getting requirements to build wheel ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25hBuilding wheels for collected packages: pymsgbox
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
-[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=0aa91e03ed30fdcf42db3522fd0444c80951cccb215529a6ed9a95fc2b8a6f0c
+[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=ef14d567d93427570776c0692e72503e364d52371ced8369846b1c6f72ea9a2b
   Stored in directory: /home/opendevin/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
 Successfully built pymsgbox
 Installing collected packages: pymsgbox

+ 6 - 6
tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython_module/prompt_004.log

@@ -126,19 +126,19 @@ search_dir(search_term: str, dir_path: str = './') -> None:
     Searches for search_term in all files in dir. If dir is not provided, searches in the current directory.
     Args:
     search_term: str: The term to search for.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
-search_file(search_term: str, file_path: Optional[str] = None) -> None:
+search_file(search_term: str, file_path: str | None = None) -> None:
     Searches for search_term in file. If file is not provided, searches in the current open file.
     Args:
     search_term: str: The term to search for.
-    file_path: Optional[str]: The path to the file to search.
+    file_path: str | None: The path to the file to search.
 
 find_file(file_name: str, dir_path: str = './') -> None:
     Finds all files with the given name in the specified directory.
     Args:
     file_name: str: The name of the file to find.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
 parse_pdf(file_path: str) -> None:
     Parses the content of a PDF file and prints it.
@@ -412,12 +412,12 @@ Sure! Let's start by installing the `pymsgbox` package.
 OBSERVATION:
 Collecting pymsgbox==1.0.9
   Downloading PyMsgBox-1.0.9.tar.gz (18 kB)
-  Installing build dependencies ... [?25l- \ | / - \ | / - \ | / - \ | / - done
+  Installing build dependencies ... [?25l- \ | / - done
 [?25h  Getting requirements to build wheel ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25hBuilding wheels for collected packages: pymsgbox
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
-[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=0aa91e03ed30fdcf42db3522fd0444c80951cccb215529a6ed9a95fc2b8a6f0c
+[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=ef14d567d93427570776c0692e72503e364d52371ced8369846b1c6f72ea9a2b
   Stored in directory: /home/opendevin/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
 Successfully built pymsgbox
 Installing collected packages: pymsgbox

+ 4 - 4
tests/integration/mock/eventstream_runtime/CodeActSWEAgent/test_ipython/prompt_001.log

@@ -114,19 +114,19 @@ search_dir(search_term: str, dir_path: str = './') -> None:
     Searches for search_term in all files in dir. If dir is not provided, searches in the current directory.
     Args:
     search_term: str: The term to search for.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
-search_file(search_term: str, file_path: Optional[str] = None) -> None:
+search_file(search_term: str, file_path: str | None = None) -> None:
     Searches for search_term in file. If file is not provided, searches in the current open file.
     Args:
     search_term: str: The term to search for.
-    file_path: Optional[str]: The path to the file to search.
+    file_path: str | None: The path to the file to search.
 
 find_file(file_name: str, dir_path: str = './') -> None:
     Finds all files with the given name in the specified directory.
     Args:
     file_name: str: The name of the file to find.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
 parse_pdf(file_path: str) -> None:
     Parses the content of a PDF file and prints it.

+ 4 - 4
tests/integration/mock/eventstream_runtime/CodeActSWEAgent/test_ipython/prompt_002.log

@@ -114,19 +114,19 @@ search_dir(search_term: str, dir_path: str = './') -> None:
     Searches for search_term in all files in dir. If dir is not provided, searches in the current directory.
     Args:
     search_term: str: The term to search for.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
-search_file(search_term: str, file_path: Optional[str] = None) -> None:
+search_file(search_term: str, file_path: str | None = None) -> None:
     Searches for search_term in file. If file is not provided, searches in the current open file.
     Args:
     search_term: str: The term to search for.
-    file_path: Optional[str]: The path to the file to search.
+    file_path: str | None: The path to the file to search.
 
 find_file(file_name: str, dir_path: str = './') -> None:
     Finds all files with the given name in the specified directory.
     Args:
     file_name: str: The name of the file to find.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
 parse_pdf(file_path: str) -> None:
     Parses the content of a PDF file and prints it.

+ 4 - 4
tests/integration/mock/eventstream_runtime/CodeActSWEAgent/test_ipython_module/prompt_001.log

@@ -114,19 +114,19 @@ search_dir(search_term: str, dir_path: str = './') -> None:
     Searches for search_term in all files in dir. If dir is not provided, searches in the current directory.
     Args:
     search_term: str: The term to search for.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
-search_file(search_term: str, file_path: Optional[str] = None) -> None:
+search_file(search_term: str, file_path: str | None = None) -> None:
     Searches for search_term in file. If file is not provided, searches in the current open file.
     Args:
     search_term: str: The term to search for.
-    file_path: Optional[str]: The path to the file to search.
+    file_path: str | None: The path to the file to search.
 
 find_file(file_name: str, dir_path: str = './') -> None:
     Finds all files with the given name in the specified directory.
     Args:
     file_name: str: The name of the file to find.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
 parse_pdf(file_path: str) -> None:
     Parses the content of a PDF file and prints it.

+ 6 - 6
tests/integration/mock/eventstream_runtime/CodeActSWEAgent/test_ipython_module/prompt_002.log

@@ -114,19 +114,19 @@ search_dir(search_term: str, dir_path: str = './') -> None:
     Searches for search_term in all files in dir. If dir is not provided, searches in the current directory.
     Args:
     search_term: str: The term to search for.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
-search_file(search_term: str, file_path: Optional[str] = None) -> None:
+search_file(search_term: str, file_path: str | None = None) -> None:
     Searches for search_term in file. If file is not provided, searches in the current open file.
     Args:
     search_term: str: The term to search for.
-    file_path: Optional[str]: The path to the file to search.
+    file_path: str | None: The path to the file to search.
 
 find_file(file_name: str, dir_path: str = './') -> None:
     Finds all files with the given name in the specified directory.
     Args:
     file_name: str: The name of the file to find.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
 parse_pdf(file_path: str) -> None:
     Parses the content of a PDF file and prints it.
@@ -603,12 +603,12 @@ Understood. Let's start by installing the `pymsgbox` package.
 OBSERVATION:
 Collecting pymsgbox==1.0.9
   Downloading PyMsgBox-1.0.9.tar.gz (18 kB)
-  Installing build dependencies ... [?25l- \ | / - \ | / - \ | / - \ | / - \ done
+  Installing build dependencies ... [?25l- \ | / - \ done
 [?25h  Getting requirements to build wheel ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25hBuilding wheels for collected packages: pymsgbox
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
-[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=e6c344aecd9e7b02d3ff2bb4d98a74d0fe6156b5f523d40b402350da7aac55e6
+[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=f7592d365a95b10c0de8a5b01ad3addd962b194ce42f0206b0b8903c7f47af51
   Stored in directory: /home/opendevin/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
 Successfully built pymsgbox
 Installing collected packages: pymsgbox

+ 6 - 6
tests/integration/mock/eventstream_runtime/CodeActSWEAgent/test_ipython_module/prompt_003.log

@@ -114,19 +114,19 @@ search_dir(search_term: str, dir_path: str = './') -> None:
     Searches for search_term in all files in dir. If dir is not provided, searches in the current directory.
     Args:
     search_term: str: The term to search for.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
-search_file(search_term: str, file_path: Optional[str] = None) -> None:
+search_file(search_term: str, file_path: str | None = None) -> None:
     Searches for search_term in file. If file is not provided, searches in the current open file.
     Args:
     search_term: str: The term to search for.
-    file_path: Optional[str]: The path to the file to search.
+    file_path: str | None: The path to the file to search.
 
 find_file(file_name: str, dir_path: str = './') -> None:
     Finds all files with the given name in the specified directory.
     Args:
     file_name: str: The name of the file to find.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
 parse_pdf(file_path: str) -> None:
     Parses the content of a PDF file and prints it.
@@ -603,12 +603,12 @@ Understood. Let's start by installing the `pymsgbox` package.
 OBSERVATION:
 Collecting pymsgbox==1.0.9
   Downloading PyMsgBox-1.0.9.tar.gz (18 kB)
-  Installing build dependencies ... [?25l- \ | / - \ | / - \ | / - \ | / - \ done
+  Installing build dependencies ... [?25l- \ | / - \ done
 [?25h  Getting requirements to build wheel ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25hBuilding wheels for collected packages: pymsgbox
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
-[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=e6c344aecd9e7b02d3ff2bb4d98a74d0fe6156b5f523d40b402350da7aac55e6
+[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=f7592d365a95b10c0de8a5b01ad3addd962b194ce42f0206b0b8903c7f47af51
   Stored in directory: /home/opendevin/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
 Successfully built pymsgbox
 Installing collected packages: pymsgbox

+ 6 - 6
tests/integration/mock/eventstream_runtime/CodeActSWEAgent/test_ipython_module/prompt_004.log

@@ -114,19 +114,19 @@ search_dir(search_term: str, dir_path: str = './') -> None:
     Searches for search_term in all files in dir. If dir is not provided, searches in the current directory.
     Args:
     search_term: str: The term to search for.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
-search_file(search_term: str, file_path: Optional[str] = None) -> None:
+search_file(search_term: str, file_path: str | None = None) -> None:
     Searches for search_term in file. If file is not provided, searches in the current open file.
     Args:
     search_term: str: The term to search for.
-    file_path: Optional[str]: The path to the file to search.
+    file_path: str | None: The path to the file to search.
 
 find_file(file_name: str, dir_path: str = './') -> None:
     Finds all files with the given name in the specified directory.
     Args:
     file_name: str: The name of the file to find.
-    dir_path: Optional[str]: The path to the directory to search.
+    dir_path: str: The path to the directory to search.
 
 parse_pdf(file_path: str) -> None:
     Parses the content of a PDF file and prints it.
@@ -603,12 +603,12 @@ Understood. Let's start by installing the `pymsgbox` package.
 OBSERVATION:
 Collecting pymsgbox==1.0.9
   Downloading PyMsgBox-1.0.9.tar.gz (18 kB)
-  Installing build dependencies ... [?25l- \ | / - \ | / - \ | / - \ | / - \ done
+  Installing build dependencies ... [?25l- \ | / - \ done
 [?25h  Getting requirements to build wheel ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25hBuilding wheels for collected packages: pymsgbox
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
-[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=e6c344aecd9e7b02d3ff2bb4d98a74d0fe6156b5f523d40b402350da7aac55e6
+[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=f7592d365a95b10c0de8a5b01ad3addd962b194ce42f0206b0b8903c7f47af51
   Stored in directory: /home/opendevin/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
 Successfully built pymsgbox
 Installing collected packages: pymsgbox