From 15c257faf0912651bf5bce0b8137941ee39497b9 Mon Sep 17 00:00:00 2001 From: Sylwia Budzynska <102833689+sylwia-budzynska@users.noreply.github.com> Date: Mon, 8 Dec 2025 21:56:13 +0000 Subject: [PATCH 01/10] Improve API endpoint error handling --- README.md | 3 ++- src/seclab_taskflow_agent/agent.py | 3 ++- src/seclab_taskflow_agent/capi.py | 24 +++++++++++++++++++----- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4e2b52c..c2b10eb 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,8 @@ Example: AI_API_TOKEN= # MCP configs GITHUB_PERSONAL_ACCESS_TOKEN= -CODEQL_DBS_BASE_PATH="/app/my_data/codeql_databases" +CODEQL_DBS_BASE_PATH="/app/data/codeql_databases" +AI_API_ENDPOINT="https://models.github.ai/inference" ``` ## Deploying from Source diff --git a/src/seclab_taskflow_agent/agent.py b/src/seclab_taskflow_agent/agent.py index 6c26b0b..98060a1 100644 --- a/src/seclab_taskflow_agent/agent.py +++ b/src/seclab_taskflow_agent/agent.py @@ -27,7 +27,8 @@ case AI_API_ENDPOINT_ENUM.AI_API_MODELS_GITHUB: default_model = 'openai/gpt-4o' case _: - raise ValueError(f"Unsupported Model Endpoint: {api_endpoint}") + raise ValueError(f"Unsupported Model Endpoint: {api_endpoint}\n" + f"Supported endpoints: {[e.to_url() for e in AI_API_ENDPOINT_ENUM]}") DEFAULT_MODEL = os.getenv('COPILOT_DEFAULT_MODEL', default=default_model) diff --git a/src/seclab_taskflow_agent/capi.py b/src/seclab_taskflow_agent/capi.py index 54744d4..702af47 100644 --- a/src/seclab_taskflow_agent/capi.py +++ b/src/seclab_taskflow_agent/capi.py @@ -11,8 +11,20 @@ # Enumeration of currently supported API endpoints. class AI_API_ENDPOINT_ENUM(StrEnum): - AI_API_MODELS_GITHUB = 'models.github.ai' - AI_API_GITHUBCOPILOT = 'api.githubcopilot.com' + AI_API_MODELS_GITHUB = 'models.github.ai' + AI_API_GITHUBCOPILOT = 'api.githubcopilot.com' + + def to_url(self): + """ + Convert the endpoint to its full URL. + """ + match self: + case self.AI_API_GITHUBCOPILOT: + return f"https://{self}" + case self.AI_API_MODELS_GITHUB: + return f"https://{self}/inference" + case _: + raise ValueError(f"Unsupported Model Endpoint: {self}") COPILOT_INTEGRATION_ID = 'vscode-chat' @@ -21,7 +33,7 @@ class AI_API_ENDPOINT_ENUM(StrEnum): # since different APIs use their own id schema, use -l with your desired # endpoint to retrieve the correct id names to use for your taskflow def get_AI_endpoint(): - return os.getenv('AI_API_ENDPOINT', default='https://models.github.ai/inference') + return os.getenv('AI_API_ENDPOINT', default='https://models.github.ai/inference') def get_AI_token(): """ @@ -65,7 +77,8 @@ def list_capi_models(token: str) -> dict[str, dict]: case AI_API_ENDPOINT_ENUM.AI_API_MODELS_GITHUB: models_list = r.json() case _: - raise ValueError(f"Unsupported Model Endpoint: {api_endpoint}") + raise ValueError(f"Unsupported Model Endpoint: {api_endpoint}\n" + f"Supported endpoints: {[e.to_url() for e in AI_API_ENDPOINT_ENUM]}") for model in models_list: models[model.get('id')] = dict(model) except httpx.RequestError as e: @@ -88,7 +101,8 @@ def supports_tool_calls(model: str, models: dict) -> bool: return 'tool-calling' in models.get(model, {}).\ get('capabilities', []) case _: - raise ValueError(f"Unsupported Model Endpoint: {api_endpoint}") + raise ValueError(f"Unsupported Model Endpoint: {api_endpoint}\n" + f"Supported endpoints: {[e.to_url() for e in AI_API_ENDPOINT_ENUM]}") def list_tool_call_models(token: str) -> dict[str, dict]: models = list_capi_models(token) From 20dbc92a1c951bb2f6d4e7ee19c260e299e020ee Mon Sep 17 00:00:00 2001 From: Sylwia Budzynska <102833689+sylwia-budzynska@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:10:26 +0100 Subject: [PATCH 02/10] Update src/seclab_taskflow_agent/agent.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/seclab_taskflow_agent/agent.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/seclab_taskflow_agent/agent.py b/src/seclab_taskflow_agent/agent.py index 98060a1..06097c1 100644 --- a/src/seclab_taskflow_agent/agent.py +++ b/src/seclab_taskflow_agent/agent.py @@ -27,8 +27,10 @@ case AI_API_ENDPOINT_ENUM.AI_API_MODELS_GITHUB: default_model = 'openai/gpt-4o' case _: - raise ValueError(f"Unsupported Model Endpoint: {api_endpoint}\n" - f"Supported endpoints: {[e.to_url() for e in AI_API_ENDPOINT_ENUM]}") + raise ValueError( + f"Unsupported Model Endpoint: {api_endpoint}\n" + f"Supported endpoints: {[e.to_url() for e in AI_API_ENDPOINT_ENUM]}" + ) DEFAULT_MODEL = os.getenv('COPILOT_DEFAULT_MODEL', default=default_model) From 59648f322b52531fa17040673e01d191b6633688 Mon Sep 17 00:00:00 2001 From: Sylwia Budzynska <102833689+sylwia-budzynska@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:10:35 +0100 Subject: [PATCH 03/10] Update src/seclab_taskflow_agent/capi.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/seclab_taskflow_agent/capi.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/seclab_taskflow_agent/capi.py b/src/seclab_taskflow_agent/capi.py index 702af47..28b663b 100644 --- a/src/seclab_taskflow_agent/capi.py +++ b/src/seclab_taskflow_agent/capi.py @@ -101,8 +101,10 @@ def supports_tool_calls(model: str, models: dict) -> bool: return 'tool-calling' in models.get(model, {}).\ get('capabilities', []) case _: - raise ValueError(f"Unsupported Model Endpoint: {api_endpoint}\n" - f"Supported endpoints: {[e.to_url() for e in AI_API_ENDPOINT_ENUM]}") + raise ValueError( + f"Unsupported Model Endpoint: {api_endpoint}\n" + f"Supported endpoints: {[e.to_url() for e in AI_API_ENDPOINT_ENUM]}" + ) def list_tool_call_models(token: str) -> dict[str, dict]: models = list_capi_models(token) From 4a27b3b6a2ec10f478737810bdec32bb8530e186 Mon Sep 17 00:00:00 2001 From: Sylwia Budzynska <102833689+sylwia-budzynska@users.noreply.github.com> Date: Mon, 8 Dec 2025 22:44:02 +0000 Subject: [PATCH 04/10] Fix to_url --- src/seclab_taskflow_agent/capi.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/seclab_taskflow_agent/capi.py b/src/seclab_taskflow_agent/capi.py index 28b663b..9f9fad2 100644 --- a/src/seclab_taskflow_agent/capi.py +++ b/src/seclab_taskflow_agent/capi.py @@ -19,12 +19,10 @@ def to_url(self): Convert the endpoint to its full URL. """ match self: - case self.AI_API_GITHUBCOPILOT: + case AI_API_ENDPOINT_ENUM.AI_API_GITHUBCOPILOT: return f"https://{self}" - case self.AI_API_MODELS_GITHUB: + case AI_API_ENDPOINT_ENUM.AI_API_MODELS_GITHUB: return f"https://{self}/inference" - case _: - raise ValueError(f"Unsupported Model Endpoint: {self}") COPILOT_INTEGRATION_ID = 'vscode-chat' @@ -62,7 +60,8 @@ def list_capi_models(token: str) -> dict[str, dict]: case AI_API_ENDPOINT_ENUM.AI_API_MODELS_GITHUB: models_catalog = 'catalog/models' case _: - raise ValueError(f"Unsupported Model Endpoint: {api_endpoint}") + raise ValueError(f"Unsupported Model Endpoint: {api_endpoint}\n" + f"Supported endpoints: {[e.to_url() for e in AI_API_ENDPOINT_ENUM]}") r = httpx.get(httpx.URL(api_endpoint).join(models_catalog), headers={ 'Accept': 'application/json', @@ -76,9 +75,6 @@ def list_capi_models(token: str) -> dict[str, dict]: models_list = r.json().get('data', []) case AI_API_ENDPOINT_ENUM.AI_API_MODELS_GITHUB: models_list = r.json() - case _: - raise ValueError(f"Unsupported Model Endpoint: {api_endpoint}\n" - f"Supported endpoints: {[e.to_url() for e in AI_API_ENDPOINT_ENUM]}") for model in models_list: models[model.get('id')] = dict(model) except httpx.RequestError as e: From 5ae974be297624b52ded0cecd2bfcc438ac8f410 Mon Sep 17 00:00:00 2001 From: Sylwia Budzynska <102833689+sylwia-budzynska@users.noreply.github.com> Date: Mon, 8 Dec 2025 22:52:38 +0000 Subject: [PATCH 05/10] Add to_url tests --- tests/test_api_endpoint_config.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_api_endpoint_config.py b/tests/test_api_endpoint_config.py index 654b44e..9fa6ba8 100644 --- a/tests/test_api_endpoint_config.py +++ b/tests/test_api_endpoint_config.py @@ -43,5 +43,13 @@ def test_api_endpoint_env_override(self): if original_env: os.environ['AI_API_ENDPOINT'] = original_env + def test_to_url_models_github(self): + endpoint = AI_API_ENDPOINT_ENUM.AI_API_MODELS_GITHUB + assert endpoint.to_url() == 'https://models.github.ai/inference' + + def test_to_url_githubcopilot(self): + endpoint = AI_API_ENDPOINT_ENUM.AI_API_GITHUBCOPILOT + assert endpoint.to_url() == 'https://api.githubcopilot.com' + if __name__ == '__main__': pytest.main([__file__, '-v']) From f12c02fc2c054632edf868e87f3a2c59c2235067 Mon Sep 17 00:00:00 2001 From: Sylwia Budzynska <102833689+sylwia-budzynska@users.noreply.github.com> Date: Mon, 8 Dec 2025 22:57:40 +0000 Subject: [PATCH 06/10] Fix code scanning note --- src/seclab_taskflow_agent/capi.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/seclab_taskflow_agent/capi.py b/src/seclab_taskflow_agent/capi.py index 9f9fad2..478ea56 100644 --- a/src/seclab_taskflow_agent/capi.py +++ b/src/seclab_taskflow_agent/capi.py @@ -23,6 +23,8 @@ def to_url(self): return f"https://{self}" case AI_API_ENDPOINT_ENUM.AI_API_MODELS_GITHUB: return f"https://{self}/inference" + case _: + raise ValueError(f"Unsupported endpoint: {self}") COPILOT_INTEGRATION_ID = 'vscode-chat' From 04d729b1fd04d2340e546334abd58f15065302a4 Mon Sep 17 00:00:00 2001 From: Sylwia Budzynska <102833689+sylwia-budzynska@users.noreply.github.com> Date: Tue, 9 Dec 2025 19:46:27 +0000 Subject: [PATCH 07/10] Add test for unsupported endpoint --- tests/test_api_endpoint_config.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/test_api_endpoint_config.py b/tests/test_api_endpoint_config.py index 9fa6ba8..e13673b 100644 --- a/tests/test_api_endpoint_config.py +++ b/tests/test_api_endpoint_config.py @@ -8,7 +8,7 @@ import pytest import os from urllib.parse import urlparse -from seclab_taskflow_agent.capi import get_AI_endpoint, AI_API_ENDPOINT_ENUM +from seclab_taskflow_agent.capi import get_AI_endpoint, AI_API_ENDPOINT_ENUM, list_capi_models class TestAPIEndpoint: """Test API endpoint configuration.""" @@ -51,5 +51,19 @@ def test_to_url_githubcopilot(self): endpoint = AI_API_ENDPOINT_ENUM.AI_API_GITHUBCOPILOT assert endpoint.to_url() == 'https://api.githubcopilot.com' + def test_unsupported_endpoint(self): + original_env = os.environ.pop('AI_API_ENDPOINT', None) + api_endpoint = 'https://unsupported.example.com' + os.environ['AI_API_ENDPOINT'] = api_endpoint + + with pytest.raises(ValueError) as excinfo: + list_capi_models("abc") + msg = str(excinfo.value) + assert 'Unsupported Model Endpoint' in msg + assert 'https://models.github.ai/inference' in msg + assert 'https://api.githubcopilot.com' in msg + if original_env: + os.environ['AI_API_ENDPOINT'] = original_env + if __name__ == '__main__': pytest.main([__file__, '-v']) From c3a54ac30cd931b8acd6914999fad717ad7ec412 Mon Sep 17 00:00:00 2001 From: Sylwia Budzynska <102833689+sylwia-budzynska@users.noreply.github.com> Date: Tue, 9 Dec 2025 20:50:48 +0100 Subject: [PATCH 08/10] Update tests/test_api_endpoint_config.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/test_api_endpoint_config.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tests/test_api_endpoint_config.py b/tests/test_api_endpoint_config.py index e13673b..dbce6f3 100644 --- a/tests/test_api_endpoint_config.py +++ b/tests/test_api_endpoint_config.py @@ -53,17 +53,18 @@ def test_to_url_githubcopilot(self): def test_unsupported_endpoint(self): original_env = os.environ.pop('AI_API_ENDPOINT', None) - api_endpoint = 'https://unsupported.example.com' - os.environ['AI_API_ENDPOINT'] = api_endpoint - - with pytest.raises(ValueError) as excinfo: - list_capi_models("abc") - msg = str(excinfo.value) - assert 'Unsupported Model Endpoint' in msg - assert 'https://models.github.ai/inference' in msg - assert 'https://api.githubcopilot.com' in msg - if original_env: - os.environ['AI_API_ENDPOINT'] = original_env + try: + api_endpoint = 'https://unsupported.example.com' + os.environ['AI_API_ENDPOINT'] = api_endpoint + with pytest.raises(ValueError) as excinfo: + list_capi_models("abc") + msg = str(excinfo.value) + assert 'Unsupported Model Endpoint' in msg + assert 'https://models.github.ai/inference' in msg + assert 'https://api.githubcopilot.com' in msg + finally: + if original_env: + os.environ['AI_API_ENDPOINT'] = original_env if __name__ == '__main__': pytest.main([__file__, '-v']) From 3f67403c3167293a046a2abe453e62ab2b3f4dc5 Mon Sep 17 00:00:00 2001 From: Sylwia Budzynska <102833689+sylwia-budzynska@users.noreply.github.com> Date: Tue, 9 Dec 2025 20:09:21 +0000 Subject: [PATCH 09/10] Fix test --- tests/test_api_endpoint_config.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_api_endpoint_config.py b/tests/test_api_endpoint_config.py index dbce6f3..a05afe3 100644 --- a/tests/test_api_endpoint_config.py +++ b/tests/test_api_endpoint_config.py @@ -44,16 +44,19 @@ def test_api_endpoint_env_override(self): os.environ['AI_API_ENDPOINT'] = original_env def test_to_url_models_github(self): + """Test to_url method for models.github.ai endpoint.""" endpoint = AI_API_ENDPOINT_ENUM.AI_API_MODELS_GITHUB assert endpoint.to_url() == 'https://models.github.ai/inference' def test_to_url_githubcopilot(self): + """Test to_url method for GitHub Copilot endpoint.""" endpoint = AI_API_ENDPOINT_ENUM.AI_API_GITHUBCOPILOT assert endpoint.to_url() == 'https://api.githubcopilot.com' def test_unsupported_endpoint(self): - original_env = os.environ.pop('AI_API_ENDPOINT', None) + """Test that unsupported API endpoint raises ValueError.""" try: + original_env = os.environ.pop('AI_API_ENDPOINT', None) api_endpoint = 'https://unsupported.example.com' os.environ['AI_API_ENDPOINT'] = api_endpoint From 3cf05ee43fe38adbb15124e77392170d4782c604 Mon Sep 17 00:00:00 2001 From: Sylwia Budzynska <102833689+sylwia-budzynska@users.noreply.github.com> Date: Wed, 10 Dec 2025 13:26:10 +0000 Subject: [PATCH 10/10] Use monkeypatch for env var --- tests/test_api_endpoint_config.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/tests/test_api_endpoint_config.py b/tests/test_api_endpoint_config.py index a05afe3..96e0216 100644 --- a/tests/test_api_endpoint_config.py +++ b/tests/test_api_endpoint_config.py @@ -53,21 +53,16 @@ def test_to_url_githubcopilot(self): endpoint = AI_API_ENDPOINT_ENUM.AI_API_GITHUBCOPILOT assert endpoint.to_url() == 'https://api.githubcopilot.com' - def test_unsupported_endpoint(self): + def test_unsupported_endpoint(self, monkeypatch): """Test that unsupported API endpoint raises ValueError.""" - try: - original_env = os.environ.pop('AI_API_ENDPOINT', None) - api_endpoint = 'https://unsupported.example.com' - os.environ['AI_API_ENDPOINT'] = api_endpoint + api_endpoint = 'https://unsupported.example.com' + monkeypatch.setenv('AI_API_ENDPOINT', api_endpoint) + with pytest.raises(ValueError) as excinfo: + list_capi_models("abc") + msg = str(excinfo.value) + assert 'Unsupported Model Endpoint' in msg + assert 'https://models.github.ai/inference' in msg + assert 'https://api.githubcopilot.com' in msg - with pytest.raises(ValueError) as excinfo: - list_capi_models("abc") - msg = str(excinfo.value) - assert 'Unsupported Model Endpoint' in msg - assert 'https://models.github.ai/inference' in msg - assert 'https://api.githubcopilot.com' in msg - finally: - if original_env: - os.environ['AI_API_ENDPOINT'] = original_env if __name__ == '__main__': pytest.main([__file__, '-v'])