From 8d302b1533f8f94b1776dacafd9ac00f8f8898a3 Mon Sep 17 00:00:00 2001 From: kagami Date: Sat, 3 Feb 2024 00:13:54 +0800 Subject: [PATCH 1/6] Expose get symbols andd find type definitions --- libs/ide_services/services.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/libs/ide_services/services.py b/libs/ide_services/services.py index 4e42e58..752335c 100644 --- a/libs/ide_services/services.py +++ b/libs/ide_services/services.py @@ -29,6 +29,16 @@ def ide_logging(level: str, message: str): pass +@rpc_call +def get_document_symbols(abspath: str): + pass + + +@rpc_call +def find_type_def_locations(abspath: str, line: int, character: int): + pass + + # NOTE: for compatibility, remove this after all usages are replaced with ide_logging def log_info(message): return ide_logging("info", message) From c31817f44a250018d5b10573d85033c352f3e61c Mon Sep 17 00:00:00 2001 From: kagami Date: Sat, 3 Feb 2024 00:14:37 +0800 Subject: [PATCH 2/6] Add type definitions for IDE service --- libs/ide_services/types.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 libs/ide_services/types.py diff --git a/libs/ide_services/types.py b/libs/ide_services/types.py new file mode 100644 index 0000000..577bb9f --- /dev/null +++ b/libs/ide_services/types.py @@ -0,0 +1,24 @@ +from typing import List +from pydantic import BaseModel + + +class Position(BaseModel): + line: int # 0-based + character: int # 0-based + + +class Range(BaseModel): + start: Position + end: Position + + +class Location(BaseModel): + abspath: str + range: Range + + +class SymbolNode(BaseModel): + name: str + kind: str + range: Range + children: List["SymbolNode"] From 2288eb480b8110372198cc40ed1e981bd6a8f6b6 Mon Sep 17 00:00:00 2001 From: kagami Date: Sat, 3 Feb 2024 00:36:17 +0800 Subject: [PATCH 3/6] Add type hint for ide services --- libs/ide_services/services.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/libs/ide_services/services.py b/libs/ide_services/services.py index 752335c..743f6b7 100644 --- a/libs/ide_services/services.py +++ b/libs/ide_services/services.py @@ -1,8 +1,11 @@ +from typing import List + from .rpc import rpc_call +from .types import Location, SymbolNode @rpc_call -def get_lsp_brige_port(): +def get_lsp_brige_port() -> str: pass @@ -12,7 +15,7 @@ def install_python_env(command_name: str, requirements_file: str) -> str: @rpc_call -def update_slash_commands(): +def update_slash_commands() -> bool: pass @@ -22,7 +25,7 @@ def ide_language() -> str: @rpc_call -def ide_logging(level: str, message: str): +def ide_logging(level: str, message: str) -> bool: """ level: "info" | "warn" | "error" | "debug" """ @@ -30,15 +33,15 @@ def ide_logging(level: str, message: str): @rpc_call -def get_document_symbols(abspath: str): +def get_document_symbols(abspath: str) -> List[SymbolNode]: pass @rpc_call -def find_type_def_locations(abspath: str, line: int, character: int): +def find_type_def_locations(abspath: str, line: int, character: int) -> List[Location]: pass # NOTE: for compatibility, remove this after all usages are replaced with ide_logging -def log_info(message): +def log_info(message) -> bool: return ide_logging("info", message) From 9efc52357756cfcda8edc10d256aefd414839ea6 Mon Sep 17 00:00:00 2001 From: kagami Date: Sat, 3 Feb 2024 22:22:42 +0800 Subject: [PATCH 4/6] Implement IDEService class --- libs/ide_services/service.py | 86 ++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 libs/ide_services/service.py diff --git a/libs/ide_services/service.py b/libs/ide_services/service.py new file mode 100644 index 0000000..2142a39 --- /dev/null +++ b/libs/ide_services/service.py @@ -0,0 +1,86 @@ +from typing import List +import os +from functools import wraps +from .types import Location, SymbolNode +import requests + +BASE_SERVER_URL = os.environ.get("DEVCHAT_IDE_SERVICE_URL", "http://localhost:3000") + + +def rpc_method(f): + """ + Decorator for Service methods + """ + + @wraps(f) + def wrapper(self, *args, **kwargs): + if os.environ.get("DEVCHAT_IDE_SERVICE_URL", "") == "": + # maybe in a test, user don't want to mock services functions + return + + try: + function_name = f.__name__ + url = f"{BASE_SERVER_URL}/{function_name}" + + data = dict(zip(f.__code__.co_varnames[1:], args)) # Exclude "self" + data.update(kwargs) + headers = {"Content-Type": "application/json"} + + response = requests.post(url, json=data, headers=headers) + + if response.status_code != 200: + raise Exception(f"Server error: {response.status_code}") + + response_data = response.json() + if "error" in response_data: + raise Exception(f"Server returned an error: {response_data['error']}") + + # Store the result in the _result attribute of the instance + self._result = response_data.get("result", None) + return f(self, *args, **kwargs) + + except ConnectionError as err: + # TODO + raise err + + return wrapper + + +class IDEService: + def __init__(self): + self._result = None + + @rpc_method + def get_lsp_brige_port(self) -> str: + return self._result + + @rpc_method + def install_python_env(self, command_name: str, requirements_file: str) -> str: + return self._result + + @rpc_method + def update_slash_commands(self) -> bool: + return self._result + + @rpc_method + def ide_language(self) -> str: + return self._result + + @rpc_method + def ide_logging(self, level: str, message: str) -> bool: + """ + level: "info" | "warn" | "error" | "debug" + """ + return self._result + + @rpc_method + def get_document_symbols(self, abspath: str) -> List[SymbolNode]: + # TODO: convert the result to a list of SymbolNode objects + return self._result + + @rpc_method + def find_type_def_locations( + self, abspath: str, line: int, character: int + ) -> List[Location]: + # TODO: convert the result to a list of Location objects + return self._result From 5f1926fcdd7c0ec6d8c5301a5a243acba97e3530 Mon Sep 17 00:00:00 2001 From: kagami Date: Sat, 3 Feb 2024 23:15:52 +0800 Subject: [PATCH 5/6] Type casting for returns of ide service --- libs/ide_services/service.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libs/ide_services/service.py b/libs/ide_services/service.py index 2142a39..af209d2 100644 --- a/libs/ide_services/service.py +++ b/libs/ide_services/service.py @@ -75,12 +75,10 @@ def ide_logging(self, level: str, message: str) -> bool: @rpc_method def get_document_symbols(self, abspath: str) -> List[SymbolNode]: - # TODO: convert the result to a list of SymbolNode objects - return self._result + return [SymbolNode.parse_obj(node) for node in self._result] @rpc_method def find_type_def_locations( self, abspath: str, line: int, character: int ) -> List[Location]: - # TODO: convert the result to a list of Location objects - return self._result + return [Location.parse_obj(loc) for loc in self._result] From ef1c9ef3cbfc53524b6825437fde1fd93aec20aa Mon Sep 17 00:00:00 2001 From: kagami Date: Sat, 3 Feb 2024 23:35:09 +0800 Subject: [PATCH 6/6] Replace the usage of services functions with IDEService client --- ask-code/ask-code.py | 4 +-- commit/commit.py | 10 +++++--- libs/ide_services/__init__.py | 14 ++--------- libs/ide_services/service.py | 19 ++++++++++---- libs/ide_services/services.py | 47 ----------------------------------- libs/ide_services/types.py | 1 + libs/llm_api/text_confirm.py | 1 - libs/llm_api/tools_call.py | 7 +++--- unit_tests/main.py | 5 ++-- 9 files changed, 32 insertions(+), 76 deletions(-) delete mode 100644 libs/ide_services/services.py diff --git a/ask-code/ask-code.py b/ask-code/ask-code.py index 0b9c49f..b21baa8 100644 --- a/ask-code/ask-code.py +++ b/ask-code/ask-code.py @@ -6,7 +6,7 @@ sys.path.append(os.path.join(os.path.dirname(__file__), "..", "libs")) sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "libs")) -from ide_services import get_lsp_brige_port # noqa: E402 +from ide_services import IDEService # noqa: E402 def query(question, lsp_brige_port): @@ -59,7 +59,7 @@ def main(): print("Usage: python index_and_query.py query [question] [port]") sys.exit(1) - port = get_lsp_brige_port() + port = IDEService().get_lsp_brige_port() question = sys.argv[2] query(question, port) diff --git a/commit/commit.py b/commit/commit.py index 9bdf214..54b7168 100644 --- a/commit/commit.py +++ b/commit/commit.py @@ -9,7 +9,7 @@ sys.path.append(os.path.dirname(__file__)) from chatmark import Checkbox, Form, TextEditor # noqa: E402 -from ide_services.services import log_info +from ide_services import IDEService # noqa: E402 from llm_api import chat_completion_stream # noqa: E402 diff_too_large_message_en = ( @@ -67,17 +67,19 @@ def read_prompt_from_file(filename): - FileNotFoundError: If the file does not exist. - Exception: If any other error occurs during file reading. """ + s = IDEService() try: with open(filename, "r", encoding="utf-8") as file: return file.read().strip() except FileNotFoundError: - log_info( + s.ide_logging( + "info", f"File {filename} not found. " - "Please make sure it exists in the same directory as the script." + "Please make sure it exists in the same directory as the script.", ) sys.exit(1) except Exception as e: - log_info(f"An error occurred while reading the file {filename}: {e}") + s.ide_logging("info", f"An error occurred while reading the file {filename}: {e}") sys.exit(1) diff --git a/libs/ide_services/__init__.py b/libs/ide_services/__init__.py index 60927ba..dbfd10a 100644 --- a/libs/ide_services/__init__.py +++ b/libs/ide_services/__init__.py @@ -1,15 +1,5 @@ -from .services import ( - get_lsp_brige_port, - ide_language, - ide_logging, - install_python_env, - update_slash_commands, -) +from .service import IDEService __all__ = [ - "get_lsp_brige_port", - "install_python_env", - "update_slash_commands", - "ide_language", - "ide_logging", + "IDEService", ] diff --git a/libs/ide_services/service.py b/libs/ide_services/service.py index af209d2..c965231 100644 --- a/libs/ide_services/service.py +++ b/libs/ide_services/service.py @@ -1,9 +1,11 @@ -from typing import List import os from functools import wraps -from .types import Location, SymbolNode +from typing import List + import requests +from .types import Location, SymbolNode + BASE_SERVER_URL = os.environ.get("DEVCHAT_IDE_SERVICE_URL", "http://localhost:3000") @@ -47,6 +49,15 @@ def wrapper(self, *args, **kwargs): class IDEService: + """ + Client for IDE service + + Usage: + client = IDEService() + res = client.ide_language() + res = client.ide_logging("info", "some message") + """ + def __init__(self): self._result = None @@ -78,7 +89,5 @@ def get_document_symbols(self, abspath: str) -> List[SymbolNode]: return [SymbolNode.parse_obj(node) for node in self._result] @rpc_method - def find_type_def_locations( - self, abspath: str, line: int, character: int - ) -> List[Location]: + def find_type_def_locations(self, abspath: str, line: int, character: int) -> List[Location]: return [Location.parse_obj(loc) for loc in self._result] diff --git a/libs/ide_services/services.py b/libs/ide_services/services.py deleted file mode 100644 index 743f6b7..0000000 --- a/libs/ide_services/services.py +++ /dev/null @@ -1,47 +0,0 @@ -from typing import List - -from .rpc import rpc_call -from .types import Location, SymbolNode - - -@rpc_call -def get_lsp_brige_port() -> str: - pass - - -@rpc_call -def install_python_env(command_name: str, requirements_file: str) -> str: - pass - - -@rpc_call -def update_slash_commands() -> bool: - pass - - -@rpc_call -def ide_language() -> str: - pass - - -@rpc_call -def ide_logging(level: str, message: str) -> bool: - """ - level: "info" | "warn" | "error" | "debug" - """ - pass - - -@rpc_call -def get_document_symbols(abspath: str) -> List[SymbolNode]: - pass - - -@rpc_call -def find_type_def_locations(abspath: str, line: int, character: int) -> List[Location]: - pass - - -# NOTE: for compatibility, remove this after all usages are replaced with ide_logging -def log_info(message) -> bool: - return ide_logging("info", message) diff --git a/libs/ide_services/types.py b/libs/ide_services/types.py index 577bb9f..dbd7ed4 100644 --- a/libs/ide_services/types.py +++ b/libs/ide_services/types.py @@ -1,4 +1,5 @@ from typing import List + from pydantic import BaseModel diff --git a/libs/llm_api/text_confirm.py b/libs/llm_api/text_confirm.py index 71ff29a..c8722ca 100644 --- a/libs/llm_api/text_confirm.py +++ b/libs/llm_api/text_confirm.py @@ -7,7 +7,6 @@ from chatmark import Checkbox, Form, TextEditor # noqa: #402 -from ide_services.services import log_info # noqa: #402 class MissEditConfirmFieldException(Exception): diff --git a/libs/llm_api/tools_call.py b/libs/llm_api/tools_call.py index 55eb1f3..e271fc2 100644 --- a/libs/llm_api/tools_call.py +++ b/libs/llm_api/tools_call.py @@ -9,7 +9,7 @@ sys.path.append(os.path.join(os.path.dirname(__file__), "..")) from chatmark import Checkbox, Form, Radio, TextEditor # noqa: #402 -from ide_services.services import log_info, log_warn # noqa: #402 +from ide_services import IDEService # noqa: #402 class MissToolsFieldException(Exception): @@ -191,9 +191,10 @@ def wrapper(*args, **kwargs): # call function functions = {tool.function_name: tool for tool in tools} for call in response["all_calls"]: - log_info( + IDEService().ide_logging( + "info", f"try to call function tool: {call['function_name']} " - f"with {call['parameters']}" + f"with {call['parameters']}", ) tool = functions[call["function_name"]] result = tool(**json.loads(call["parameters"])) diff --git a/unit_tests/main.py b/unit_tests/main.py index 2909113..4e98945 100644 --- a/unit_tests/main.py +++ b/unit_tests/main.py @@ -10,7 +10,7 @@ from chatmark import Checkbox, Form, Step, TextEditor # noqa: E402 from find_reference_tests import find_reference_tests from i18n import TUILanguage, get_translation -from ide_services import ide_language # noqa: E402 +from ide_services import IDEService # noqa: E402 from model import ( FuncToTest, TokenBudgetExceededException, @@ -214,7 +214,8 @@ def main(input: str): user_prompt = f"Help me write unit tests for the `{func_name}` function" repo_root = os.getcwd() - ide_lang = ide_language() + ide_lang = IDEService().ide_language() + tui_lang = TUILanguage.from_str(ide_lang) _i = get_translation(tui_lang)