Refactor capi.py: provider pattern for endpoint-specific logic#187
Refactor capi.py: provider pattern for endpoint-specific logic#187anticomputer merged 2 commits intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors the CAPI integration to centralize endpoint-specific behavior behind an APIProvider registry, reducing scattered endpoint conditionals and preventing Copilot-only headers from being sent to other providers.
Changes:
- Introduces an
APIProviderdataclass + provider registry to encapsulate per-endpoint logic (catalog path, parsing, tool-call detection, headers). - Updates model catalog + tool-call capability helpers to delegate behavior to the selected provider.
- Updates the agent client construction to only attach provider-specific headers (e.g.,
Copilot-Integration-Id) when appropriate, and adjusts tests accordingly.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/seclab_taskflow_agent/capi.py |
Replaces endpoint enum/match blocks with provider registry and provider-based model/tool-call logic. |
src/seclab_taskflow_agent/agent.py |
Uses provider-derived defaults and ensures provider-specific headers are only sent to matching endpoints. |
tests/test_capi_extended.py |
Updates/expands tests to cover provider registry and new tool-call capability semantics. |
tests/test_api_endpoint_config.py |
Removes enum-based assertions and adds provider base URL resolution coverage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
p-
left a comment
There was a problem hiding this comment.
We just changed the default models this morning
b4b89c0 to
5776ca1
Compare
anticomputer
left a comment
There was a problem hiding this comment.
Rebased on main, addressed feedback:
Default models (p-): Updated all provider defaults to gpt-4.1 to match main.
base_url unused (copilot-reviewer): list_capi_models() now uses provider.base_url as the canonical URL instead of the raw parameter. Known providers always resolve to their configured base URL.
_DEFAULT_PROVIDER dead code (copilot-reviewer): Removed. get_provider() constructs the fallback inline.
check_tool_calls default (copilot-reviewer): Keeping bool(model_info) as the base default. The previous heuristic ("gpt-" in model.lower()) was worse — it missed o1, o3-mini, o3, and any non-gpt model. Defaulting to False would break custom endpoints that don't publish capability metadata. This only affects CLI -l listing, not runtime behavior.
5776ca1 to
11538ac
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (4)
src/seclab_taskflow_agent/capi.py:169
httpx.URL(base).join(provider.models_catalog)will drop the last path segment whenbasehas a path and doesn’t end with/(urljoin semantics). With the OpenAI provider’sbase_url="https://api.openai.com/v1"andmodels_catalog="models", this will likely requesthttps://api.openai.com/modelsinstead of.../v1/models. Consider either (a) ensuring provider base URLs intended for joining end with a trailing slash, or (b) makingmodels_catalogan absolute/fully-qualified path for providers like OpenAI (e.g.,v1/models) so the final catalog URL is correct.
r = httpx.get(
httpx.URL(base).join(provider.models_catalog),
headers=headers,
src/seclab_taskflow_agent/capi.py:66
APIProvider.check_tool_calls()currently defaults toreturn bool(model_info), which makeslist_tool_call_models()treat every catalog entry as tool-call capable for providers that don’t override this (notably the OpenAI provider). That contradicts thelist_tool_call_modelsdocstring (“only models that support tool calls”) and regresses prior OpenAI filtering behavior. Suggest adding an explicit OpenAI provider implementation (or other reliable rule) so non-chat models (embeddings/audio/etc.) don’t get incorrectly reported as tool-call capable.
def check_tool_calls(self, _model: str, model_info: dict) -> bool:
"""Return True if *model* supports tool calls according to its catalog entry."""
# Default: optimistically assume support when present in catalog
return bool(model_info)
src/seclab_taskflow_agent/capi.py:124
- For unknown endpoints,
get_provider()returnsAPIProvider(name="custom", base_url=url)which inherits the defaultdefault_model="gpt-4.1". This silently changes the previous behavior where unknown endpoints forced an explicit default model, and it can cause confusing runtime failures for custom/self-hosted providers that don’t have agpt-4.1model. Consider setting a sentinel/emptydefault_modelfor the custom provider (or requiringCOPILOT_DEFAULT_MODELwhenname=="custom") to preserve the explicit configuration requirement.
def get_provider(endpoint: str | None = None) -> APIProvider:
"""Return the ``APIProvider`` for the given (or configured) endpoint URL."""
url = endpoint or get_AI_endpoint()
netloc = urlparse(url).netloc
provider = _PROVIDERS.get(netloc)
if provider is not None:
return provider
# Unknown endpoint — return a generic provider with the given base URL
return APIProvider(name="custom", base_url=url)
src/seclab_taskflow_agent/capi.py:197
list_tool_call_models()behavior changed to use the provider registry (and now depends on each provider’scheck_tool_callsimplementation), but there are no direct tests coveringlist_tool_call_modelsfor the built-in providers (Copilot/GitHub Models/OpenAI). Adding tests here would help catch regressions like over-including non-tool-capable models for OpenAI or incorrect catalog URL construction.
def list_tool_call_models(token: str, endpoint: str | None = None) -> dict[str, dict]:
"""Return only models that support tool calls."""
models = list_capi_models(token, endpoint)
provider = get_provider(endpoint)
return {
mid: info
for mid, info in models.items()
if provider.check_tool_calls(mid, info)
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
11538ac to
e1029a9
Compare
e1029a9 to
01041fd
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/seclab_taskflow_agent/capi.py:188
- The catalog URL is constructed with
httpx.URL(base).join(provider.models_catalog). Whenbase_urlincludes a path segment like/v1(OpenAI) or a custom endpoint with versioned paths,URL.join()treats the base path as a file and can drop the last segment (e.g.https://api.openai.com/v1+modelsbecomeshttps://api.openai.com/models). This will breaklist_capi_models()for those endpoints. Consider storingmodels_catalogas an absolute path (e.g./v1/models) or adjusting URL construction so it reliably appends/overrides paths as intended for each provider.
r = httpx.get(
httpx.URL(base).join(provider.models_catalog),
headers=headers,
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Consolidate endpoint-specific logic (catalog path, response parsing, tool-call detection, headers) into an APIProvider dataclass with a hostname-keyed registry. Adding a new endpoint is now a single registry entry instead of changes across three match/case blocks. - Remove AI_API_ENDPOINT_ENUM StrEnum and strenum dependency - Only send Copilot-Integration-Id header to Copilot endpoints - Accept optional endpoint parameter in public functions - Drop fragile "gpt-" substring heuristic for OpenAI tool-call check - Update tests to use new provider API
01041fd to
13aef99
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@kevinbackhouse heads up — this PR has merge-order conflicts with #186, #189, #195 (and mildly #191, #203, #205). Those PRs fix linter rules in the StrEnum/to_url()/match-case code that this refactor deletes entirely. If they land first we'd just be resolving conflicts against dead code. Cleanest path is to merge this first, then rebase the lint-fix PRs — the capi.py hunks become no-ops and the agent.py changes should still apply. |
0acd002 to
e71c52b
Compare
e71c52b to
3c42d67
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
3c42d67 to
a0e4d3a
Compare
Replace the StrEnum + three scattered match/case blocks with a single APIProvider dataclass registry. Endpoint-specific behavior (catalog path, response parsing, tool-call detection, headers) now lives in one place per provider. Copilot-Integration-Id header is no longer leaked to non-Copilot endpoints.