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
4 changes: 2 additions & 2 deletions src/seclab_taskflow_agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
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, get_AI_endpoint, get_AI_token, AI_API_ENDPOINT_ENUM
from .capi import get_AI_endpoint, get_AI_token, get_custom_header, AI_API_ENDPOINT_ENUM

# grab our secrets from .env, this must be in .gitignore
load_dotenv(find_dotenv(usecwd=True))
Expand Down Expand Up @@ -156,7 +156,7 @@ def __init__(self,
agent_hooks: TaskAgentHooks | None = None):
client = AsyncOpenAI(base_url=api_endpoint,
api_key=get_AI_token(),
default_headers={'Copilot-Integration-Id': COPILOT_INTEGRATION_ID})
default_headers=get_custom_header())
set_default_openai_client(client)
# CAPI does not yet support the Responses API: https://github.com/github/copilot-api/issues/11185
# as such we are implementing on chat completions for now
Expand Down
38 changes: 30 additions & 8 deletions src/seclab_taskflow_agent/capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ def to_url(self):
case _:
raise ValueError(f"Unsupported endpoint: {self}")

COPILOT_INTEGRATION_ID = 'vscode-chat'

# you can also set https://api.githubcopilot.com if you prefer
# but beware that your taskflows need to reference the correct model id
# since different APIs use their own id schema, use -l with your desired
Expand All @@ -52,6 +50,31 @@ def get_AI_token():
return token
raise RuntimeError("AI_API_TOKEN environment variable is not set.")

def get_custom_header() -> dict[str, str]:
"""
Get custom header from environment variable AI_API_CUSTOM_HEADER.
Expected format: name:value
Returns a dictionary that can be merged into request headers.
"""
custom_header = os.getenv('AI_API_CUSTOM_HEADER')
if not custom_header:
return {}

# Split on first colon to handle values that might contain colons
parts = custom_header.split(':', 1)
if len(parts) != 2:
logging.warning(f"Invalid AI_API_CUSTOM_HEADER format. Expected 'name:value', got: {custom_header}")
return {}

name, value = parts
name = name.strip()
value = value.strip()
if not name or not value:
logging.warning(f"Invalid AI_API_CUSTOM_HEADER: header name and value must be non-empty after stripping. Got: '{custom_header}'")
return {}
return {name: value}
Comment on lines +64 to +75
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
parts = custom_header.split(':', 1)
if len(parts) != 2:
logging.warning(f"Invalid AI_API_CUSTOM_HEADER format. Expected 'name:value', got: {custom_header}")
return {}
name, value = parts
name = name.strip()
value = value.strip()
if not name or not value:
logging.warning(f"Invalid AI_API_CUSTOM_HEADER: header name and value must be non-empty after stripping. Got: '{custom_header}'")
return {}
return {name: value}
return json.loads(custom_header)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there a simplified version of JSON supported by the json.loads API?
AI_API_CUSTOM_HEADER={\"key\":\"val\"} seems like a complication of things.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Not that I know of. I agree that it makes the syntax more clunky. But it simplifies the code a lot and adds support for multiple fields, which somebody might find useful. This isn't going to be a frequently used feature is it?

Copy link
Contributor

Choose a reason for hiding this comment

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

It is actually needed all the time after removing the default integration id, so I'd rather not having to do something like AI_API_CUSTOM_HEADER={\"key\":\"val\"} all the time. Besides nobody will use it for multiple header if they need to add that all those dashes everywhere.



# 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"""
Expand All @@ -69,12 +92,11 @@ def list_capi_models(token: str) -> dict[str, dict]:
case _:
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',
'Authorization': f'Bearer {token}',
'Copilot-Integration-Id': COPILOT_INTEGRATION_ID
})
headers = {
'Accept': 'application/json',
'Authorization': f'Bearer {token}',
} | get_custom_header()
r = httpx.get(httpx.URL(api_endpoint).join(models_catalog), headers=headers)
r.raise_for_status()
# CAPI vs Models API
match netloc:
Expand Down