From 4f92519fe1b997bb7149435ecd5f0f17b13b42c8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 17:26:32 +0000 Subject: [PATCH 01/11] Initial plan From 6e73c3cd9847fc5546ba4c2cca47ffa25d7b6595 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 17:34:28 +0000 Subject: [PATCH 02/11] Change default API endpoint to models.github.ai/inference and rename COPILOT_API_ENDPOINT to AI_API_ENDPOINT Co-authored-by: kevinbackhouse <4358136+kevinbackhouse@users.noreply.github.com> --- src/seclab_taskflow_agent/agent.py | 8 +++--- src/seclab_taskflow_agent/capi.py | 18 ++++++------- tests/test_yaml_parser.py | 41 ++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/src/seclab_taskflow_agent/agent.py b/src/seclab_taskflow_agent/agent.py index d49c781..59221e5 100644 --- a/src/seclab_taskflow_agent/agent.py +++ b/src/seclab_taskflow_agent/agent.py @@ -15,18 +15,18 @@ from agents.run import RunHooks from agents import Agent, Runner, AgentHooks, RunHooks, result, function_tool, Tool, RunContextWrapper, TContext, OpenAIChatCompletionsModel, set_default_openai_client, set_default_openai_api, set_tracing_disabled -from .capi import COPILOT_INTEGRATION_ID, COPILOT_API_ENDPOINT +from .capi import COPILOT_INTEGRATION_ID, AI_API_ENDPOINT # grab our secrets from .env, this must be in .gitignore load_dotenv(find_dotenv(usecwd=True)) -match urlparse(COPILOT_API_ENDPOINT).netloc: +match urlparse(AI_API_ENDPOINT).netloc: case 'api.githubcopilot.com': default_model = 'gpt-4o' case 'models.github.ai': default_model = 'openai/gpt-4o' case _: - raise ValueError(f"Unsupported Model Endpoint: {COPILOT_API_ENDPOINT}") + raise ValueError(f"Unsupported Model Endpoint: {AI_API_ENDPOINT}") DEFAULT_MODEL = os.getenv('COPILOT_DEFAULT_MODEL', default=default_model) @@ -148,7 +148,7 @@ def __init__(self, model_settings: ModelSettings | None = None, run_hooks: TaskRunHooks | None = None, agent_hooks: TaskAgentHooks | None = None): - client = AsyncOpenAI(base_url=COPILOT_API_ENDPOINT, + client = AsyncOpenAI(base_url=AI_API_ENDPOINT, api_key=os.getenv('COPILOT_TOKEN'), default_headers={'Copilot-Integration-Id': COPILOT_INTEGRATION_ID}) set_default_openai_client(client) diff --git a/src/seclab_taskflow_agent/capi.py b/src/seclab_taskflow_agent/capi.py index 735dd9a..374373e 100644 --- a/src/seclab_taskflow_agent/capi.py +++ b/src/seclab_taskflow_agent/capi.py @@ -8,11 +8,11 @@ import os from urllib.parse import urlparse -# you can also set https://models.github.ai/inference if you prefer +# you can also set https://api.githubcopilot.com if you prefer # but beware that your taskflows need to reference the correct model id -# since the Modeld API uses it's own id schema, use -l with your desired +# 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 -COPILOT_API_ENDPOINT = os.getenv('COPILOT_API_ENDPOINT', default='https://api.githubcopilot.com') +AI_API_ENDPOINT = os.getenv('AI_API_ENDPOINT', default='https://models.github.ai/inference') COPILOT_INTEGRATION_ID = 'vscode-chat' # assume we are >= python 3.9 for our type hints @@ -20,14 +20,14 @@ def list_capi_models(token: str) -> dict[str, dict]: """Retrieve a dictionary of available CAPI models""" models = {} try: - match urlparse(COPILOT_API_ENDPOINT).netloc: + match urlparse(AI_API_ENDPOINT).netloc: case 'api.githubcopilot.com': models_catalog = 'models' case 'models.github.ai': models_catalog = 'catalog/models' case _: - raise ValueError(f"Unsupported Model Endpoint: {COPILOT_API_ENDPOINT}") - r = httpx.get(httpx.URL(COPILOT_API_ENDPOINT).join(models_catalog), + raise ValueError(f"Unsupported Model Endpoint: {AI_API_ENDPOINT}") + r = httpx.get(httpx.URL(AI_API_ENDPOINT).join(models_catalog), headers={ 'Accept': 'application/json', 'Authorization': f'Bearer {token}', @@ -35,7 +35,7 @@ def list_capi_models(token: str) -> dict[str, dict]: }) r.raise_for_status() # CAPI vs Models API - match urlparse(COPILOT_API_ENDPOINT).netloc: + match urlparse(AI_API_ENDPOINT).netloc: case 'api.githubcopilot.com': models_list = r.json().get('data', []) case 'models.github.ai': @@ -51,7 +51,7 @@ def list_capi_models(token: str) -> dict[str, dict]: return models def supports_tool_calls(model: str, models: dict) -> bool: - match urlparse(COPILOT_API_ENDPOINT).netloc: + match urlparse(AI_API_ENDPOINT).netloc: case 'api.githubcopilot.com': return models.get(model, {}).\ get('capabilities', {}).\ @@ -61,7 +61,7 @@ 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: {COPILOT_API_ENDPOINT}") + raise ValueError(f"Unsupported Model Endpoint: {AI_API_ENDPOINT}") def list_tool_call_models(token: str) -> dict[str, dict]: models = list_capi_models(token) diff --git a/tests/test_yaml_parser.py b/tests/test_yaml_parser.py index 9887a54..94ff58e 100644 --- a/tests/test_yaml_parser.py +++ b/tests/test_yaml_parser.py @@ -11,6 +11,7 @@ import tempfile from pathlib import Path import yaml +import os from seclab_taskflow_agent.available_tools import AvailableTools class TestYamlParser: @@ -104,5 +105,45 @@ def test_globals_in_taskflow_file(self): assert 'globals' in taskflow assert taskflow['globals']['test_var'] == 'default_value' +class TestAPIEndpoint: + """Test API endpoint configuration.""" + + def test_default_api_endpoint(self): + """Test that default API endpoint is set to models.github.ai/inference.""" + from seclab_taskflow_agent.capi import AI_API_ENDPOINT + # When no env var is set, it should default to models.github.ai/inference + # Note: We can't easily test this without manipulating the environment + # so we'll just import and verify the constant exists + assert AI_API_ENDPOINT is not None + assert isinstance(AI_API_ENDPOINT, str) + + def test_api_endpoint_env_override(self): + """Test that AI_API_ENDPOINT can be overridden by environment variable.""" + # Save original env + original_env = os.environ.get('AI_API_ENDPOINT') + + try: + # Set custom endpoint + test_endpoint = 'https://test.example.com' + os.environ['AI_API_ENDPOINT'] = test_endpoint + + # Need to reload the module to pick up the new env var + import importlib + import seclab_taskflow_agent.capi + importlib.reload(seclab_taskflow_agent.capi) + + from seclab_taskflow_agent.capi import AI_API_ENDPOINT + assert AI_API_ENDPOINT == test_endpoint + finally: + # Restore original env + if original_env is None: + os.environ.pop('AI_API_ENDPOINT', None) + else: + os.environ['AI_API_ENDPOINT'] = original_env + # Reload again to restore original state + import importlib + import seclab_taskflow_agent.capi + importlib.reload(seclab_taskflow_agent.capi) + if __name__ == '__main__': pytest.main([__file__, '-v']) From e29ab429376fed442d573db28317cf68ad89377c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 17:35:43 +0000 Subject: [PATCH 03/11] Refactor test to remove code duplication Co-authored-by: kevinbackhouse <4358136+kevinbackhouse@users.noreply.github.com> --- tests/test_yaml_parser.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/test_yaml_parser.py b/tests/test_yaml_parser.py index 94ff58e..23aae85 100644 --- a/tests/test_yaml_parser.py +++ b/tests/test_yaml_parser.py @@ -108,6 +108,13 @@ def test_globals_in_taskflow_file(self): class TestAPIEndpoint: """Test API endpoint configuration.""" + @staticmethod + def _reload_capi_module(): + """Helper method to reload the capi module.""" + import importlib + import seclab_taskflow_agent.capi + importlib.reload(seclab_taskflow_agent.capi) + def test_default_api_endpoint(self): """Test that default API endpoint is set to models.github.ai/inference.""" from seclab_taskflow_agent.capi import AI_API_ENDPOINT @@ -127,10 +134,8 @@ def test_api_endpoint_env_override(self): test_endpoint = 'https://test.example.com' os.environ['AI_API_ENDPOINT'] = test_endpoint - # Need to reload the module to pick up the new env var - import importlib - import seclab_taskflow_agent.capi - importlib.reload(seclab_taskflow_agent.capi) + # Reload the module to pick up the new env var + self._reload_capi_module() from seclab_taskflow_agent.capi import AI_API_ENDPOINT assert AI_API_ENDPOINT == test_endpoint @@ -141,9 +146,7 @@ def test_api_endpoint_env_override(self): else: os.environ['AI_API_ENDPOINT'] = original_env # Reload again to restore original state - import importlib - import seclab_taskflow_agent.capi - importlib.reload(seclab_taskflow_agent.capi) + self._reload_capi_module() if __name__ == '__main__': pytest.main([__file__, '-v']) From 9ac4db36b028d65ace589f4663560f5bae6f3800 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 1 Dec 2025 13:28:17 +0000 Subject: [PATCH 04/11] Improvements to the code written by Copilot. --- pyproject.toml | 1 + src/seclab_taskflow_agent/capi.py | 25 ++++--- tests/test_api_endpoint_config.py | 61 +++++++++++++++++ tests/test_cli_parser.py | 78 ++++++++++++++++++++++ tests/test_yaml_parser.py | 105 +----------------------------- 5 files changed, 158 insertions(+), 112 deletions(-) create mode 100644 tests/test_api_endpoint_config.py create mode 100644 tests/test_cli_parser.py diff --git a/pyproject.toml b/pyproject.toml index 0a3b1b6..0a9eb71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,6 +97,7 @@ dependencies = [ "SQLAlchemy==2.0.41", "sse-starlette==2.4.1", "starlette==0.49.1", + "strenum==0.4.15", "tqdm==4.67.1", "typer==0.16.0", "types-requests==2.32.4.20250611", diff --git a/src/seclab_taskflow_agent/capi.py b/src/seclab_taskflow_agent/capi.py index 374373e..650c0b5 100644 --- a/src/seclab_taskflow_agent/capi.py +++ b/src/seclab_taskflow_agent/capi.py @@ -6,6 +6,7 @@ import json import logging import os +from strenum import StrEnum from urllib.parse import urlparse # you can also set https://api.githubcopilot.com if you prefer @@ -13,6 +14,11 @@ # 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 AI_API_ENDPOINT = os.getenv('AI_API_ENDPOINT', default='https://models.github.ai/inference') + +class AI_API_ENDPOINT_ENUM(StrEnum): + AI_API_MODELS_GITHUB = 'models.github.ai' + AI_API_GITHUBCOPILOT = 'api.githubcopilot.com' + COPILOT_INTEGRATION_ID = 'vscode-chat' # assume we are >= python 3.9 for our type hints @@ -20,10 +26,11 @@ def list_capi_models(token: str) -> dict[str, dict]: """Retrieve a dictionary of available CAPI models""" models = {} try: - match urlparse(AI_API_ENDPOINT).netloc: - case 'api.githubcopilot.com': + netloc = urlparse(AI_API_ENDPOINT).netloc + match netloc: + case AI_API_ENDPOINT_ENUM.AI_API_GITHUBCOPILOT: models_catalog = 'models' - case 'models.github.ai': + case AI_API_ENDPOINT_ENUM.AI_API_MODELS_GITHUB: models_catalog = 'catalog/models' case _: raise ValueError(f"Unsupported Model Endpoint: {AI_API_ENDPOINT}") @@ -35,11 +42,13 @@ def list_capi_models(token: str) -> dict[str, dict]: }) r.raise_for_status() # CAPI vs Models API - match urlparse(AI_API_ENDPOINT).netloc: - case 'api.githubcopilot.com': + match netloc: + case AI_API_ENDPOINT_ENUM.AI_API_GITHUBCOPILOT: models_list = r.json().get('data', []) - case 'models.github.ai': + case AI_API_ENDPOINT_ENUM.AI_API_MODELS_GITHUB: models_list = r.json() + case _: + raise ValueError(f"Unsupported Model Endpoint: {AI_API_ENDPOINT}") for model in models_list: models[model.get('id')] = dict(model) except httpx.RequestError as e: @@ -52,12 +61,12 @@ def list_capi_models(token: str) -> dict[str, dict]: def supports_tool_calls(model: str, models: dict) -> bool: match urlparse(AI_API_ENDPOINT).netloc: - case 'api.githubcopilot.com': + case AI_API_ENDPOINT_ENUM.AI_API_GITHUBCOPILOT: return models.get(model, {}).\ get('capabilities', {}).\ get('supports', {}).\ get('tool_calls', False) - case 'models.github.ai': + case AI_API_ENDPOINT_ENUM.AI_API_MODELS_GITHUB: return 'tool-calling' in models.get(model, {}).\ get('capabilities', []) case _: diff --git a/tests/test_api_endpoint_config.py b/tests/test_api_endpoint_config.py new file mode 100644 index 0000000..5c547b0 --- /dev/null +++ b/tests/test_api_endpoint_config.py @@ -0,0 +1,61 @@ +# SPDX-FileCopyrightText: 2025 GitHub +# SPDX-License-Identifier: MIT + +""" +Test API endpoint configuration. +""" + +import pytest +import tempfile +from pathlib import Path +import yaml +import os +from urllib.parse import urlparse +from seclab_taskflow_agent.available_tools import AvailableTools + +class TestAPIEndpoint: + """Test API endpoint configuration.""" + + @staticmethod + def _reload_capi_module(): + """Helper method to reload the capi module.""" + import importlib + import seclab_taskflow_agent.capi + importlib.reload(seclab_taskflow_agent.capi) + + def test_default_api_endpoint(self): + """Test that default API endpoint is set to models.github.ai/inference.""" + from seclab_taskflow_agent.capi import AI_API_ENDPOINT, AI_API_ENDPOINT_ENUM + # When no env var is set, it should default to models.github.ai/inference + # Note: We can't easily test this without manipulating the environment + # so we'll just import and verify the constant exists + assert AI_API_ENDPOINT is not None + assert isinstance(AI_API_ENDPOINT, str) + assert urlparse(AI_API_ENDPOINT).netloc == AI_API_ENDPOINT_ENUM.AI_API_MODELS_GITHUB + + def test_api_endpoint_env_override(self): + """Test that AI_API_ENDPOINT can be overridden by environment variable.""" + # Save original env + original_env = os.environ.get('AI_API_ENDPOINT') + + try: + # Set custom endpoint + test_endpoint = 'https://test.example.com' + os.environ['AI_API_ENDPOINT'] = test_endpoint + + # Reload the module to pick up the new env var + self._reload_capi_module() + + from seclab_taskflow_agent.capi import AI_API_ENDPOINT + assert AI_API_ENDPOINT == test_endpoint + finally: + # Restore original env + if original_env is None: + os.environ.pop('AI_API_ENDPOINT', None) + else: + os.environ['AI_API_ENDPOINT'] = original_env + # Reload again to restore original state + self._reload_capi_module() + +if __name__ == '__main__': + pytest.main([__file__, '-v']) diff --git a/tests/test_cli_parser.py b/tests/test_cli_parser.py new file mode 100644 index 0000000..6387830 --- /dev/null +++ b/tests/test_cli_parser.py @@ -0,0 +1,78 @@ +# SPDX-FileCopyrightText: 2025 GitHub +# SPDX-License-Identifier: MIT + +""" +Test CLI global variable parsing. +""" + +import pytest +import tempfile +from pathlib import Path +import yaml +import os +from urllib.parse import urlparse +from seclab_taskflow_agent.available_tools import AvailableTools + +class TestCliGlobals: + """Test CLI global variable parsing.""" + + def test_parse_single_global(self): + """Test parsing a single global variable from command line.""" + from seclab_taskflow_agent.__main__ import parse_prompt_args + available_tools = AvailableTools() + + p, t, l, cli_globals, user_prompt, _ = parse_prompt_args( + available_tools, "-t example -g fruit=apples") + + assert t == "example" + assert cli_globals == {"fruit": "apples"} + assert p is None + assert l is False + + def test_parse_multiple_globals(self): + """Test parsing multiple global variables from command line.""" + from seclab_taskflow_agent.__main__ import parse_prompt_args + available_tools = AvailableTools() + + p, t, l, cli_globals, user_prompt, _ = parse_prompt_args( + available_tools, "-t example -g fruit=apples -g color=red") + + assert t == "example" + assert cli_globals == {"fruit": "apples", "color": "red"} + assert p is None + assert l is False + + def test_parse_global_with_spaces(self): + """Test parsing global variables with spaces in values.""" + from seclab_taskflow_agent.__main__ import parse_prompt_args + available_tools = AvailableTools() + + p, t, l, cli_globals, user_prompt, _ = parse_prompt_args( + available_tools, "-t example -g message=hello world") + + assert t == "example" + # "world" becomes part of the prompt, not the value + assert cli_globals == {"message": "hello"} + assert "world" in user_prompt + + def test_parse_global_with_equals_in_value(self): + """Test parsing global variables with equals sign in value.""" + from seclab_taskflow_agent.__main__ import parse_prompt_args + available_tools = AvailableTools() + + p, t, l, cli_globals, user_prompt, _ = parse_prompt_args( + available_tools, "-t example -g equation=x=5") + + assert t == "example" + assert cli_globals == {"equation": "x=5"} + + def test_globals_in_taskflow_file(self): + """Test that globals can be read from taskflow file.""" + available_tools = AvailableTools() + + taskflow = available_tools.get_taskflow("tests.data.test_globals_taskflow") + assert 'globals' in taskflow + assert taskflow['globals']['test_var'] == 'default_value' + +if __name__ == '__main__': + pytest.main([__file__, '-v']) diff --git a/tests/test_yaml_parser.py b/tests/test_yaml_parser.py index 23aae85..a9b7e64 100644 --- a/tests/test_yaml_parser.py +++ b/tests/test_yaml_parser.py @@ -12,6 +12,7 @@ from pathlib import Path import yaml import os +from urllib.parse import urlparse from seclab_taskflow_agent.available_tools import AvailableTools class TestYamlParser: @@ -44,109 +45,5 @@ def test_parse_example_taskflows(self): assert len(example_task_flow['taskflow']) == 4 # 4 tasks in taskflow assert example_task_flow['taskflow'][0]['task']['max_steps'] == 20 -class TestCliGlobals: - """Test CLI global variable parsing.""" - - def test_parse_single_global(self): - """Test parsing a single global variable from command line.""" - from seclab_taskflow_agent.__main__ import parse_prompt_args - available_tools = AvailableTools() - - p, t, l, cli_globals, user_prompt, _ = parse_prompt_args( - available_tools, "-t example -g fruit=apples") - - assert t == "example" - assert cli_globals == {"fruit": "apples"} - assert p is None - assert l is False - - def test_parse_multiple_globals(self): - """Test parsing multiple global variables from command line.""" - from seclab_taskflow_agent.__main__ import parse_prompt_args - available_tools = AvailableTools() - - p, t, l, cli_globals, user_prompt, _ = parse_prompt_args( - available_tools, "-t example -g fruit=apples -g color=red") - - assert t == "example" - assert cli_globals == {"fruit": "apples", "color": "red"} - assert p is None - assert l is False - - def test_parse_global_with_spaces(self): - """Test parsing global variables with spaces in values.""" - from seclab_taskflow_agent.__main__ import parse_prompt_args - available_tools = AvailableTools() - - p, t, l, cli_globals, user_prompt, _ = parse_prompt_args( - available_tools, "-t example -g message=hello world") - - assert t == "example" - # "world" becomes part of the prompt, not the value - assert cli_globals == {"message": "hello"} - assert "world" in user_prompt - - def test_parse_global_with_equals_in_value(self): - """Test parsing global variables with equals sign in value.""" - from seclab_taskflow_agent.__main__ import parse_prompt_args - available_tools = AvailableTools() - - p, t, l, cli_globals, user_prompt, _ = parse_prompt_args( - available_tools, "-t example -g equation=x=5") - - assert t == "example" - assert cli_globals == {"equation": "x=5"} - - def test_globals_in_taskflow_file(self): - """Test that globals can be read from taskflow file.""" - available_tools = AvailableTools() - - taskflow = available_tools.get_taskflow("tests.data.test_globals_taskflow") - assert 'globals' in taskflow - assert taskflow['globals']['test_var'] == 'default_value' - -class TestAPIEndpoint: - """Test API endpoint configuration.""" - - @staticmethod - def _reload_capi_module(): - """Helper method to reload the capi module.""" - import importlib - import seclab_taskflow_agent.capi - importlib.reload(seclab_taskflow_agent.capi) - - def test_default_api_endpoint(self): - """Test that default API endpoint is set to models.github.ai/inference.""" - from seclab_taskflow_agent.capi import AI_API_ENDPOINT - # When no env var is set, it should default to models.github.ai/inference - # Note: We can't easily test this without manipulating the environment - # so we'll just import and verify the constant exists - assert AI_API_ENDPOINT is not None - assert isinstance(AI_API_ENDPOINT, str) - - def test_api_endpoint_env_override(self): - """Test that AI_API_ENDPOINT can be overridden by environment variable.""" - # Save original env - original_env = os.environ.get('AI_API_ENDPOINT') - - try: - # Set custom endpoint - test_endpoint = 'https://test.example.com' - os.environ['AI_API_ENDPOINT'] = test_endpoint - - # Reload the module to pick up the new env var - self._reload_capi_module() - - from seclab_taskflow_agent.capi import AI_API_ENDPOINT - assert AI_API_ENDPOINT == test_endpoint - finally: - # Restore original env - if original_env is None: - os.environ.pop('AI_API_ENDPOINT', None) - else: - os.environ['AI_API_ENDPOINT'] = original_env - # Reload again to restore original state - self._reload_capi_module() - if __name__ == '__main__': pytest.main([__file__, '-v']) From 3a9f5466460c85f3219e63c4d463b683d29a347f Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 1 Dec 2025 13:37:24 +0000 Subject: [PATCH 05/11] Remove unused imports --- tests/test_api_endpoint_config.py | 3 --- tests/test_cli_parser.py | 5 ----- tests/test_yaml_parser.py | 5 ----- 3 files changed, 13 deletions(-) diff --git a/tests/test_api_endpoint_config.py b/tests/test_api_endpoint_config.py index 5c547b0..f8a42c4 100644 --- a/tests/test_api_endpoint_config.py +++ b/tests/test_api_endpoint_config.py @@ -6,9 +6,6 @@ """ import pytest -import tempfile -from pathlib import Path -import yaml import os from urllib.parse import urlparse from seclab_taskflow_agent.available_tools import AvailableTools diff --git a/tests/test_cli_parser.py b/tests/test_cli_parser.py index 6387830..f93701f 100644 --- a/tests/test_cli_parser.py +++ b/tests/test_cli_parser.py @@ -6,11 +6,6 @@ """ import pytest -import tempfile -from pathlib import Path -import yaml -import os -from urllib.parse import urlparse from seclab_taskflow_agent.available_tools import AvailableTools class TestCliGlobals: diff --git a/tests/test_yaml_parser.py b/tests/test_yaml_parser.py index a9b7e64..c035da6 100644 --- a/tests/test_yaml_parser.py +++ b/tests/test_yaml_parser.py @@ -8,11 +8,6 @@ """ import pytest -import tempfile -from pathlib import Path -import yaml -import os -from urllib.parse import urlparse from seclab_taskflow_agent.available_tools import AvailableTools class TestYamlParser: From 6df166d806efbdbf5f4d558c2fd85e85bde30457 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 1 Dec 2025 13:45:17 +0000 Subject: [PATCH 06/11] Change import style to work around CodeQL alert. --- tests/test_api_endpoint_config.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/test_api_endpoint_config.py b/tests/test_api_endpoint_config.py index f8a42c4..0b336fe 100644 --- a/tests/test_api_endpoint_config.py +++ b/tests/test_api_endpoint_config.py @@ -22,13 +22,14 @@ def _reload_capi_module(): def test_default_api_endpoint(self): """Test that default API endpoint is set to models.github.ai/inference.""" - from seclab_taskflow_agent.capi import AI_API_ENDPOINT, AI_API_ENDPOINT_ENUM + import seclab_taskflow_agent.capi # When no env var is set, it should default to models.github.ai/inference # Note: We can't easily test this without manipulating the environment # so we'll just import and verify the constant exists - assert AI_API_ENDPOINT is not None - assert isinstance(AI_API_ENDPOINT, str) - assert urlparse(AI_API_ENDPOINT).netloc == AI_API_ENDPOINT_ENUM.AI_API_MODELS_GITHUB + endpoint = seclab_taskflow_agent.capi.AI_API_ENDPOINT + assert endpoint is not None + assert isinstance(endpoint, str) + assert urlparse(endpoint).netloc == AI_API_ENDPOINT_ENUM.AI_API_MODELS_GITHUB def test_api_endpoint_env_override(self): """Test that AI_API_ENDPOINT can be overridden by environment variable.""" @@ -43,8 +44,8 @@ def test_api_endpoint_env_override(self): # Reload the module to pick up the new env var self._reload_capi_module() - from seclab_taskflow_agent.capi import AI_API_ENDPOINT - assert AI_API_ENDPOINT == test_endpoint + import seclab_taskflow_agent.capi + assert seclab_taskflow_agent.capi.AI_API_ENDPOINT == test_endpoint finally: # Restore original env if original_env is None: From f16f1dc3438195640c04ce7ac051964b7386f3f7 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 1 Dec 2025 13:47:03 +0000 Subject: [PATCH 07/11] Fix test error --- tests/test_api_endpoint_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_api_endpoint_config.py b/tests/test_api_endpoint_config.py index 0b336fe..3d8109f 100644 --- a/tests/test_api_endpoint_config.py +++ b/tests/test_api_endpoint_config.py @@ -29,7 +29,7 @@ def test_default_api_endpoint(self): endpoint = seclab_taskflow_agent.capi.AI_API_ENDPOINT assert endpoint is not None assert isinstance(endpoint, str) - assert urlparse(endpoint).netloc == AI_API_ENDPOINT_ENUM.AI_API_MODELS_GITHUB + assert urlparse(endpoint).netloc == seclab_taskflow_agent.capi.AI_API_ENDPOINT_ENUM.AI_API_MODELS_GITHUB def test_api_endpoint_env_override(self): """Test that AI_API_ENDPOINT can be overridden by environment variable.""" From 90724ab665922a1dc50e5badc506e47753fe129c Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 1 Dec 2025 13:49:46 +0000 Subject: [PATCH 08/11] Remove unused import --- tests/test_api_endpoint_config.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_api_endpoint_config.py b/tests/test_api_endpoint_config.py index 3d8109f..fc0d887 100644 --- a/tests/test_api_endpoint_config.py +++ b/tests/test_api_endpoint_config.py @@ -8,7 +8,6 @@ import pytest import os from urllib.parse import urlparse -from seclab_taskflow_agent.available_tools import AvailableTools class TestAPIEndpoint: """Test API endpoint configuration.""" From f0c4ef150afd2bd19e83d38894791b451c976782 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 1 Dec 2025 13:56:02 +0000 Subject: [PATCH 09/11] Use Enum --- src/seclab_taskflow_agent/agent.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/seclab_taskflow_agent/agent.py b/src/seclab_taskflow_agent/agent.py index 59221e5..f05d764 100644 --- a/src/seclab_taskflow_agent/agent.py +++ b/src/seclab_taskflow_agent/agent.py @@ -15,15 +15,15 @@ from agents.run import RunHooks from agents import Agent, Runner, AgentHooks, RunHooks, result, function_tool, Tool, RunContextWrapper, TContext, OpenAIChatCompletionsModel, set_default_openai_client, set_default_openai_api, set_tracing_disabled -from .capi import COPILOT_INTEGRATION_ID, AI_API_ENDPOINT +from .capi import COPILOT_INTEGRATION_ID, AI_API_ENDPOINT, AI_API_ENDPOINT_ENUM # grab our secrets from .env, this must be in .gitignore load_dotenv(find_dotenv(usecwd=True)) match urlparse(AI_API_ENDPOINT).netloc: - case 'api.githubcopilot.com': + case AI_API_ENDPOINT_ENUM.AI_API_GITHUBCOPILOT: default_model = 'gpt-4o' - case 'models.github.ai': + case AI_API_ENDPOINT_ENUM.AI_API_MODELS_GITHUB: default_model = 'openai/gpt-4o' case _: raise ValueError(f"Unsupported Model Endpoint: {AI_API_ENDPOINT}") From 5acf9faabeadf76a6339c360a9e833b512a04c6a Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 1 Dec 2025 13:57:19 +0000 Subject: [PATCH 10/11] Add comment --- src/seclab_taskflow_agent/capi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/seclab_taskflow_agent/capi.py b/src/seclab_taskflow_agent/capi.py index 650c0b5..ed3779a 100644 --- a/src/seclab_taskflow_agent/capi.py +++ b/src/seclab_taskflow_agent/capi.py @@ -15,6 +15,7 @@ # endpoint to retrieve the correct id names to use for your taskflow AI_API_ENDPOINT = os.getenv('AI_API_ENDPOINT', default='https://models.github.ai/inference') +# Enumeration of currently support API endpoints. class AI_API_ENDPOINT_ENUM(StrEnum): AI_API_MODELS_GITHUB = 'models.github.ai' AI_API_GITHUBCOPILOT = 'api.githubcopilot.com' From 0aad2278708eeb56e4e981d4a4fd54e99bf3d97c Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 1 Dec 2025 14:17:05 +0000 Subject: [PATCH 11/11] Update src/seclab_taskflow_agent/capi.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/seclab_taskflow_agent/capi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seclab_taskflow_agent/capi.py b/src/seclab_taskflow_agent/capi.py index ed3779a..8b52f00 100644 --- a/src/seclab_taskflow_agent/capi.py +++ b/src/seclab_taskflow_agent/capi.py @@ -15,7 +15,7 @@ # endpoint to retrieve the correct id names to use for your taskflow AI_API_ENDPOINT = os.getenv('AI_API_ENDPOINT', default='https://models.github.ai/inference') -# Enumeration of currently support API endpoints. +# 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'