Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/seclab_taskflow_agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down
18 changes: 9 additions & 9 deletions src/seclab_taskflow_agent/capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,34 @@
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
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),

Check failure

Code scanning / CodeQL

Potentially uninitialized local variable Error

Local variable 'models_catalog' may be used before it is initialized.

Copilot Autofix

AI 1 day ago

To eliminate the risk (and to satisfy static analysis tools), the local variable models_catalog should be initialized before being used in line 30. The best way to fix this while preserving existing behavior is to define models_catalog as None before the match block. Then, after the control flow, perform a check to ensure it was set for supported cases, and if not, raise an appropriate error. This ensures that the variable is always defined prior to its usage and makes control flow more robust and readable. Only lines in the region of the models_catalog assignment in list_capi_models need changing.

Suggested changeset 1
src/seclab_taskflow_agent/capi.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/seclab_taskflow_agent/capi.py b/src/seclab_taskflow_agent/capi.py
--- a/src/seclab_taskflow_agent/capi.py
+++ b/src/seclab_taskflow_agent/capi.py
@@ -20,6 +20,7 @@
     """Retrieve a dictionary of available CAPI models"""
     models = {}
     try:
+        models_catalog = None
         match urlparse(AI_API_ENDPOINT).netloc:
             case 'api.githubcopilot.com':
                 models_catalog = 'models'
@@ -27,6 +28,9 @@
                 models_catalog = 'catalog/models'
             case _:
                 raise ValueError(f"Unsupported Model Endpoint: {AI_API_ENDPOINT}")
+        # Ensure models_catalog was set (should always be the case here)
+        if models_catalog is None:
+            raise ValueError(f"Model catalog could not be determined for endpoint: {AI_API_ENDPOINT}")
         r = httpx.get(httpx.URL(AI_API_ENDPOINT).join(models_catalog),
                       headers={
                           'Accept': 'application/json',
EOF
@@ -20,6 +20,7 @@
"""Retrieve a dictionary of available CAPI models"""
models = {}
try:
models_catalog = None
match urlparse(AI_API_ENDPOINT).netloc:
case 'api.githubcopilot.com':
models_catalog = 'models'
@@ -27,6 +28,9 @@
models_catalog = 'catalog/models'
case _:
raise ValueError(f"Unsupported Model Endpoint: {AI_API_ENDPOINT}")
# Ensure models_catalog was set (should always be the case here)
if models_catalog is None:
raise ValueError(f"Model catalog could not be determined for endpoint: {AI_API_ENDPOINT}")
r = httpx.get(httpx.URL(AI_API_ENDPOINT).join(models_catalog),
headers={
'Accept': 'application/json',
Copilot is powered by AI and may make mistakes. Always verify output.
headers={
'Accept': 'application/json',
'Authorization': f'Bearer {token}',
'Copilot-Integration-Id': COPILOT_INTEGRATION_ID
})
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':
Expand All @@ -51,7 +51,7 @@
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', {}).\
Expand All @@ -61,7 +61,7 @@
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)
Expand Down
44 changes: 44 additions & 0 deletions tests/test_yaml_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import tempfile
from pathlib import Path
import yaml
import os
from seclab_taskflow_agent.available_tools import AvailableTools

class TestYamlParser:
Expand Down Expand Up @@ -104,5 +105,48 @@
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

Check notice

Code scanning / CodeQL

Module is imported with 'import' and 'import from' Note test

Module 'seclab_taskflow_agent.capi' is imported with both 'import' and 'import from'.

Copilot Autofix

AI 1 day ago

To fix this problem, we should remove all instances of from seclab_taskflow_agent.capi import AI_API_ENDPOINT, relying instead on using import seclab_taskflow_agent.capi and referencing seclab_taskflow_agent.capi.AI_API_ENDPOINT wherever necessary. Since seclab_taskflow_agent.capi is imported and reloaded in both the setup (_reload_capi_module) and in the tests, referencing the attribute directly ensures that we always get the current value after reload. The affected code is within the TestAPIEndpoint class: lines 120-125 and 140-141. The change will be to replace the direct import with attribute access and remove the from ... import ... import statements.

Suggested changeset 1
tests/test_yaml_parser.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/tests/test_yaml_parser.py b/tests/test_yaml_parser.py
--- a/tests/test_yaml_parser.py
+++ b/tests/test_yaml_parser.py
@@ -117,12 +117,12 @@
     
     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
+        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 seclab_taskflow_agent.capi.AI_API_ENDPOINT is not None
+        assert isinstance(seclab_taskflow_agent.capi.AI_API_ENDPOINT, str)
     
     def test_api_endpoint_env_override(self):
         """Test that AI_API_ENDPOINT can be overridden by environment variable."""
@@ -137,8 +134,8 @@
             # 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:
EOF
@@ -117,12 +117,12 @@

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
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 seclab_taskflow_agent.capi.AI_API_ENDPOINT is not None
assert isinstance(seclab_taskflow_agent.capi.AI_API_ENDPOINT, str)

def test_api_endpoint_env_override(self):
"""Test that AI_API_ENDPOINT can be overridden by environment variable."""
@@ -137,8 +134,8 @@
# 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:
Copilot is powered by AI and may make mistakes. Always verify output.
importlib.reload(seclab_taskflow_agent.capi)
Comment on lines +115 to +116
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module 'seclab_taskflow_agent.capi' is imported with both 'import' and 'import from'.

Suggested change
import seclab_taskflow_agent.capi
importlib.reload(seclab_taskflow_agent.capi)
capi_mod = importlib.import_module("seclab_taskflow_agent.capi")
importlib.reload(capi_mod)

Copilot uses AI. Check for mistakes.

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)
Comment on lines +118 to +125
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test claims to verify that the default API endpoint is set to models.github.ai/inference but only checks that AI_API_ENDPOINT exists and is a string. Consider asserting the actual default value when the environment variable is not set, or update the test description to match what it actually tests. For example:

def test_default_api_endpoint(self):
    """Test that default API endpoint is models.github.ai/inference when env var is not set."""
    # Clear any existing env var
    original_env = os.environ.pop('AI_API_ENDPOINT', None)
    try:
        self._reload_capi_module()
        from seclab_taskflow_agent.capi import AI_API_ENDPOINT
        assert AI_API_ENDPOINT == 'https://models.github.ai/inference'
    finally:
        if original_env is not None:
            os.environ['AI_API_ENDPOINT'] = original_env
        self._reload_capi_module()

Copilot uses AI. Check for mistakes.

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'])
Loading