Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e11ea15
Add Kilocode provider support
ShmidtS Jan 31, 2026
ece8d3a
refactor(cooldown): 🐛 change from provider-level to credential-level …
ShmidtS Feb 8, 2026
ec7d93a
fix(headers): 🐛 replace client auth headers with correct provider hea…
ShmidtS Feb 9, 2026
52ebc4d
same fix
ShmidtS Feb 9, 2026
4fbe556
fix(security): 🐛 prevent client header leakage and improve error reco…
ShmidtS Feb 12, 2026
46e99ae
refactor(model): 🔧 add safe model string parsing with consistent erro…
ShmidtS Feb 12, 2026
31c884d
feat(token): ✨ add automatic max_tokens calculation and Kilocode prov…
ShmidtS Feb 13, 2026
dbec473
Revise README content and remove unnecessary badges
ShmidtS Feb 13, 2026
b458835
Added start batch file
ShmidtS Feb 13, 2026
07bf628
fix(client): improve HTTP client resilience and streaming error handling
ShmidtS Feb 14, 2026
5d1922e
feat(perf): add performance optimization modules and context overflow…
ShmidtS Feb 15, 2026
d550021
feat(token): add context overflow detection and provider-specific saf…
ShmidtS Feb 15, 2026
36aa94c
feat(streaming): add precomputed input tokens fallback for provider c…
ShmidtS Feb 16, 2026
c9043e0
perf: optimize API interaction with connection pooling and caching
ShmidtS Feb 18, 2026
cdc2486
fix(client): reset LiteLLM HTTP client cache on connection errors
ShmidtS Feb 19, 2026
3317fcb
fix(warmup): use configured API_BASE for connection warmup
ShmidtS Feb 19, 2026
33e06f4
fix(litellm): normalize invalid finish_reason values from providers
ShmidtS Feb 23, 2026
1f8d665
feat(resilience): add circuit breaker and IP throttle detection
ShmidtS Feb 24, 2026
e907994
perf: optimize HTTP connection pooling and add memory limits
ShmidtS Feb 25, 2026
b7a5346
ShmidtS Feb 25, 2026
a5789c7
feat(opencode): add OpenCode AI provider support
ShmidtS Mar 2, 2026
0920df9
feat(quota): intelligent quota failure tracking and inception support
ShmidtS Mar 5, 2026
6e34ed8
refactor(client): extract _strip_client_headers method and improve st…
ShmidtS Mar 5, 2026
f9cb1fa
refactor: extract BaseTokenManager and _prepare_request_context for DRY
ShmidtS Mar 6, 2026
c58d07c
perf: maximize parallelism and throughput for API provider requests
ShmidtS Mar 6, 2026
9401d51
feat(trybons): add TryBons Anthropic-compatible provider
ShmidtS Mar 6, 2026
4934876
refactor(nvidia): sanitize Anthropic-specific fields and improve thin…
ShmidtS Mar 6, 2026
09ff3a5
feat(inception): add backoff env overrides and server error classific…
ShmidtS Mar 6, 2026
82bb900
feat(client): retry same key with backoff for mid-stream transient er…
ShmidtS Mar 6, 2026
decad17
fix(client): allow concurrent reuse of provider keys
ShmidtS Mar 8, 2026
1da0792
fix(usage): reuse active keys before taking idle ones
ShmidtS Mar 8, 2026
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
6 changes: 6 additions & 0 deletions src/proxy_app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
import argparse
import logging

# Fix Windows console encoding issues
if sys.platform == "win32":
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')

# --- Argument Parsing (BEFORE heavy imports) ---
parser = argparse.ArgumentParser(description="API Key Proxy Server")
parser.add_argument(
Expand Down
1 change: 1 addition & 0 deletions src/proxy_app/provider_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"cohere": "https://api.cohere.ai/v1",
"bedrock": "https://bedrock-runtime.us-east-1.amazonaws.com",
"openrouter": "https://openrouter.ai/api/v1",
"kilocode": "https://kilocode.ai/api/openrouter",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The PR description mentions https://kilocode.ai/api/openrouter/v1/chat/completions as the endpoint, but here /v1 is missing. Please verify if Kilocode requires /v1 in the path for OpenRouter compatibility.

Comment thread
greptile-apps[bot] marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Incorrect URL will be constructed for Kilocode requests

The get_provider_endpoint function checks whether base_url ends with "/v1" or "/v1/openai" to decide how to append the action path (line 74). Since "https://kilocode.ai/api/openrouter" matches neither suffix, the function falls through to the generic fallback (line 78):

return f"{base_url}/v1/{action}"

This produces https://kilocode.ai/api/openrouter/v1/chat/completions, but the documented Kilocode endpoint is https://kilocode.ai/api/openrouter/chat/completions — no /v1/ segment. This means all requests will hit a non-existent path.

To align with how LiteLLM constructs the URL, add a Kilocode-specific case before the fallback, or structure the entry to end with /v1:

Suggested change
"kilocode": "https://kilocode.ai/api/openrouter",
elif provider == "kilocode":
if action == "chat/completions":
return f"{base_url}/chat/completions"
return f"{base_url}/{action}"

}

def get_provider_endpoint(provider: str, model_name: str, incoming_path: str) -> Optional[str]:
Expand Down
116 changes: 64 additions & 52 deletions src/rotator_library/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,9 +443,9 @@ def __init__(
custom_caps[provider][tier_key][model_key] = {}

# Store max_requests value
custom_caps[provider][tier_key][model_key]["max_requests"] = (
env_value
)
custom_caps[provider][tier_key][model_key][
"max_requests"
] = env_value

elif env_key.startswith(cooldown_prefix):
# Parse cooldown config
Expand Down Expand Up @@ -1476,9 +1476,9 @@ async def _execute_with_retry(
model=model,
attempt=attempt + 1,
error=e,
request_headers=dict(request.headers)
if request
else {},
request_headers=(
dict(request.headers) if request else {}
),
)

# Record in accumulator for client reporting
Expand Down Expand Up @@ -1519,9 +1519,9 @@ async def _execute_with_retry(
model=model,
attempt=attempt + 1,
error=e,
request_headers=dict(request.headers)
if request
else {},
request_headers=(
dict(request.headers) if request else {}
),
)
classified_error = classify_error(e, provider=provider)
error_message = str(e).split("\n")[0]
Expand Down Expand Up @@ -1569,9 +1569,9 @@ async def _execute_with_retry(
model=model,
attempt=attempt + 1,
error=e,
request_headers=dict(request.headers)
if request
else {},
request_headers=(
dict(request.headers) if request else {}
),
)
classified_error = classify_error(e, provider=provider)
error_message = str(e).split("\n")[0]
Expand Down Expand Up @@ -1654,14 +1654,22 @@ async def _execute_with_retry(

if "gemma-3" in model and "messages" in litellm_kwargs:
litellm_kwargs["messages"] = [
{"role": "user", "content": m["content"]}
if m.get("role") == "system"
else m
(
{"role": "user", "content": m["content"]}
if m.get("role") == "system"
else m
)
for m in litellm_kwargs["messages"]
]

litellm_kwargs = sanitize_request_payload(litellm_kwargs, model)

# If the provider is 'nvidia', set the custom provider to 'nvidia_nim'
# and strip the prefix from the model name for LiteLLM.
if provider == "nvidia":
litellm_kwargs["custom_llm_provider"] = "nvidia_nim"
litellm_kwargs["model"] = model.split("/", 1)[1]
Comment on lines +2361 to +2365

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This nvidia provider fix (setting custom_llm_provider and stripping the model prefix) should also be applied to _make_streaming_request (around line 2445) to ensure consistent behavior across both request types.


for attempt in range(self.max_retries):
try:
lib_logger.info(
Expand Down Expand Up @@ -1716,9 +1724,9 @@ async def _execute_with_retry(
model=model,
attempt=attempt + 1,
error=e,
request_headers=dict(request.headers)
if request
else {},
request_headers=(
dict(request.headers) if request else {}
),
)
classified_error = classify_error(e, provider=provider)

Expand Down Expand Up @@ -1760,9 +1768,9 @@ async def _execute_with_retry(
model=model,
attempt=attempt + 1,
error=e,
request_headers=dict(request.headers)
if request
else {},
request_headers=(
dict(request.headers) if request else {}
),
)
classified_error = classify_error(e, provider=provider)
error_message = str(e).split("\n")[0]
Expand Down Expand Up @@ -1815,9 +1823,9 @@ async def _execute_with_retry(
model=model,
attempt=attempt + 1,
error=e,
request_headers=dict(request.headers)
if request
else {},
request_headers=(
dict(request.headers) if request else {}
),
)

classified_error = classify_error(e, provider=provider)
Expand Down Expand Up @@ -1878,9 +1886,9 @@ async def _execute_with_retry(
model=model,
attempt=attempt + 1,
error=e,
request_headers=dict(request.headers)
if request
else {},
request_headers=(
dict(request.headers) if request else {}
),
)

if request and await request.is_disconnected():
Expand Down Expand Up @@ -2257,9 +2265,9 @@ async def _streaming_acompletion_with_retry(
model=model,
attempt=attempt + 1,
error=e,
request_headers=dict(request.headers)
if request
else {},
request_headers=(
dict(request.headers) if request else {}
),
)

# Record in accumulator for client reporting
Expand Down Expand Up @@ -2302,9 +2310,9 @@ async def _streaming_acompletion_with_retry(
model=model,
attempt=attempt + 1,
error=e,
request_headers=dict(request.headers)
if request
else {},
request_headers=(
dict(request.headers) if request else {}
),
)
classified_error = classify_error(e, provider=provider)
error_message = str(e).split("\n")[0]
Expand Down Expand Up @@ -2352,9 +2360,9 @@ async def _streaming_acompletion_with_retry(
model=model,
attempt=attempt + 1,
error=e,
request_headers=dict(request.headers)
if request
else {},
request_headers=(
dict(request.headers) if request else {}
),
)
classified_error = classify_error(e, provider=provider)
error_message = str(e).split("\n")[0]
Expand Down Expand Up @@ -2426,9 +2434,11 @@ async def _streaming_acompletion_with_retry(

if "gemma-3" in model and "messages" in litellm_kwargs:
litellm_kwargs["messages"] = [
{"role": "user", "content": m["content"]}
if m.get("role") == "system"
else m
(
{"role": "user", "content": m["content"]}
if m.get("role") == "system"
else m
)
for m in litellm_kwargs["messages"]
]

Expand Down Expand Up @@ -2533,9 +2543,9 @@ async def _streaming_acompletion_with_retry(
model=model,
attempt=attempt + 1,
error=e,
request_headers=dict(request.headers)
if request
else {},
request_headers=(
dict(request.headers) if request else {}
),
raw_response_text=cleaned_str,
)

Expand Down Expand Up @@ -2629,9 +2639,9 @@ async def _streaming_acompletion_with_retry(
model=model,
attempt=attempt + 1,
error=e,
request_headers=dict(request.headers)
if request
else {},
request_headers=(
dict(request.headers) if request else {}
),
)
classified_error = classify_error(e, provider=provider)
error_message_text = str(e).split("\n")[0]
Expand Down Expand Up @@ -2680,9 +2690,9 @@ async def _streaming_acompletion_with_retry(
model=model,
attempt=attempt + 1,
error=e,
request_headers=dict(request.headers)
if request
else {},
request_headers=(
dict(request.headers) if request else {}
),
)
classified_error = classify_error(e, provider=provider)
error_message_text = str(e).split("\n")[0]
Expand Down Expand Up @@ -3127,7 +3137,9 @@ async def get_quota_stats(
group_stats["total_requests_remaining"] = 0
# Fallback to avg_remaining_pct when max_requests unavailable
# This handles providers like Firmware that only provide percentage
group_stats["total_remaining_pct"] = group_stats.get("avg_remaining_pct")
group_stats["total_remaining_pct"] = group_stats.get(
"avg_remaining_pct"
)

prov_stats["quota_groups"][group_name] = group_stats

Expand Down Expand Up @@ -3334,9 +3346,9 @@ async def force_refresh_quota(
"""
result = {
"action": "force_refresh",
"scope": "credential"
if credential
else ("provider" if provider else "all"),
"scope": (
"credential" if credential else ("provider" if provider else "all")
),
"provider": provider,
"credential": credential,
"credentials_refreshed": 0,
Expand Down
6 changes: 6 additions & 0 deletions src/rotator_library/provider_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@
("OPENROUTER_API_BASE", "API Base URL (optional)", None),
],
},
"kilocode": {
"category": "popular",
"extra_vars": [
("KILOCODE_API_BASE", "API Base URL (optional)", None),
],
},
"groq": {
"category": "popular",
},
Expand Down
37 changes: 37 additions & 0 deletions src/rotator_library/providers/kilocode_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# SPDX-License-Identifier: LGPL-3.0-only
# Copyright (c) 2026 Mirrowel

import httpx
import logging
from typing import List
from .provider_interface import ProviderInterface

lib_logger = logging.getLogger('rotator_library')
lib_logger.propagate = False # Ensure this logger doesn't propagate to root
if not lib_logger.handlers:
lib_logger.addHandler(logging.NullHandler())

class KilocodeProvider(ProviderInterface):
"""
Provider implementation for the Kilocode API.

Kilocode routes requests to various providers through model prefixes:
- minimax/minimax-m2.1:free
- moonshotai/kimi-k2.5:free
- z-ai/glm-4.7:free
- And other provider/model combinations
"""
async def get_models(self, api_key: str, client: httpx.AsyncClient) -> List[str]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Inconsistency: Unlike OpencodeProvider which uses os.getenv("OPENCODE_API_BASE", ...), this provider hardcodes the URL (with the wrong domain). Consider adding KILOCODE_API_BASE environment variable support for consistency and flexibility.

api_base = os.getenv("KILOCODE_API_BASE", "https://kilocode.ai/api/openrouter").rstrip("/")
response = await client.get(
    f"{api_base}/models",
    ...
)

"""
Fetches the list of available models from the Kilocode API.
"""
try:
response = await client.get(
"https://kilocode.ai/api/openrouter/models",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This URL is hardcoded and ignores the KILOCODE_API_BASE environment variable defined in provider_config.py. Consider using the environment variable if available, falling back to this URL as the default.

headers={"Authorization": f"Bearer {api_key}"}
)
Comment on lines +25 to +33

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hardcoded URL ignores KILOCODE_API_BASE environment variable

The get_models method hardcodes the base URL instead of respecting the KILOCODE_API_BASE environment variable that is already configured in provider_config.py. For consistency with OpencodeProvider and to allow users to override the endpoint, use the env var with a sensible default:

Suggested change
async def get_models(self, api_key: str, client: httpx.AsyncClient) -> List[str]:
"""
Fetches the list of available models from the Kilocode API.
"""
try:
response = await client.get(
"https://kilo.ai/api/openrouter/models",
headers={"Authorization": f"Bearer {api_key}"},
)
async def get_models(self, api_key: str, client: httpx.AsyncClient) -> List[str]:
"""
Fetches the list of available models from the Kilocode API.
"""
import os
api_base = os.getenv("KILOCODE_API_BASE", "https://kilocode.ai/api/openrouter").rstrip("/")
try:
response = await client.get(
f"{api_base}/models",
headers={"Authorization": f"Bearer {api_key}"},
)

response.raise_for_status()
return [f"kilocode/{model['id']}" for model in response.json().get("data", [])]
except httpx.RequestError as e:
Comment on lines +36 to +38

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Uncaught httpx.HTTPStatusError exception

response.raise_for_status() raises httpx.HTTPStatusError for non-2xx HTTP responses (401, 429, 500, etc.), but only httpx.RequestError is caught on line 38. HTTPStatusError is not a subclass of RequestError — they descend from different branches of the exception hierarchy. Any HTTP error response will propagate as an unhandled exception instead of being caught gracefully.

Suggested change
f"kilocode/{model['id']}" for model in response.json().get("data", [])
]
except httpx.RequestError as e:
except (httpx.RequestError, httpx.HTTPStatusError) as e:
lib_logger.error(f"Failed to fetch Kilocode models: {e}")
return []

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Uncaught httpx.HTTPStatusError exception

response.raise_for_status() (line 34) raises httpx.HTTPStatusError for non-2xx HTTP responses (401, 429, 500, etc.). However, only httpx.RequestError is caught on line 38. HTTPStatusError is a sibling exception (both descend from HTTPError via different branches), so it will propagate uncaught for any API errors.

Add httpx.HTTPError to the except clause to catch both:

Suggested change
except httpx.RequestError as e:
except (httpx.RequestError, httpx.HTTPError) as e:

lib_logger.error(f"Failed to fetch Kilocode models: {e}")
return []
Comment on lines +25 to +40

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Inconsistent with OpencodeProvider which uses configurable OPENCODE_API_BASE. Consider using KILOCODE_API_BASE env var here for consistency:

import os
api_base = os.getenv("KILOCODE_API_BASE", "https://kilocode.ai/api/openrouter").rstrip("/")
response = await client.get(
    f"{api_base}/models",
    headers={"Authorization": f"Bearer {api_key}"},
)
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/rotator_library/providers/kilocode_provider.py
Line: 28-43

Comment:
Inconsistent with `OpencodeProvider` which uses configurable `OPENCODE_API_BASE`. Consider using `KILOCODE_API_BASE` env var here for consistency:

```python
import os
api_base = os.getenv("KILOCODE_API_BASE", "https://kilocode.ai/api/openrouter").rstrip("/")
response = await client.get(
    f"{api_base}/models",
    headers={"Authorization": f"Bearer {api_key}"},
)
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +29 to +40

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Uncaught httpx.HTTPStatusError exception

response.raise_for_status() (line 34) raises httpx.HTTPStatusError for non-2xx HTTP responses (401, 429, 500, etc.). However, only httpx.RequestError is caught on line 38. HTTPStatusError is not a subclass of RequestError — it descends from HTTPError via a different branch. Any HTTP error response will propagate as an unhandled exception instead of being caught and returning an empty list.

Suggested change
try:
response = await client.get(
"https://kilo.ai/api/openrouter/models",
headers={"Authorization": f"Bearer {api_key}"},
)
response.raise_for_status()
return [
f"kilocode/{model['id']}" for model in response.json().get("data", [])
]
except httpx.RequestError as e:
lib_logger.error(f"Failed to fetch Kilocode models: {e}")
return []
try:
response = await client.get(
"https://kilocode.ai/api/openrouter/models",
headers={"Authorization": f"Bearer {api_key}"},
)
response.raise_for_status()
return [
f"kilocode/{model['id']}" for model in response.json().get("data", [])
]
except (httpx.RequestError, httpx.HTTPStatusError) as e:
lib_logger.error(f"Failed to fetch Kilocode models: {e}")
return []

Comment on lines +25 to +40

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Wrong domain in get_models endpoint and hardcoded URL ignores KILOCODE_API_BASE env var

Line 31 uses https://kilo.ai/api/openrouter/models (wrong domain — should be kilocode.ai), and the URL is hardcoded instead of respecting the KILOCODE_API_BASE environment variable that is already configured in provider_config.py.

This breaks model discovery and ignores user overrides. For consistency with OpencodeProvider, use:

import os
api_base = os.getenv("KILOCODE_API_BASE", "https://kilocode.ai/api/openrouter").rstrip("/")
response = await client.get(
    f"{api_base}/models",
    headers={"Authorization": f"Bearer {api_key}"},
)

Comment on lines +29 to +40

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Uncaught httpx.HTTPStatusError exception

response.raise_for_status() (line 34) raises httpx.HTTPStatusError for non-2xx responses, but only httpx.RequestError is caught on line 38. HTTPStatusError is a sibling exception (both descend from HTTPError via different branches), so it will propagate uncaught for any API errors (401, 429, 500, etc.).

Add httpx.HTTPError to the except clause:

except (httpx.RequestError, httpx.HTTPError) as e:

Comment on lines +34 to +40

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In httpx, does response.raise_for_status()raiseHTTPStatusError, and is HTTPStatusErrorseparate fromRequestErrorunderHTTPError?

💡 Result:

Yes.

  • response.raise_for_status() may raise httpx.HTTPStatusError when the response status is an error (4xx or 5xx). [1]
  • In HTTPX’s exception hierarchy, HTTPStatusError is separate from RequestError: both are direct subclasses of httpx.HTTPError (i.e., they’re siblings under HTTPError). [1]

Source(s):
[1] HTTPX docs — “Exceptions” page (exception hierarchy and HTTPStatusError docs). (python-httpx.org)

Citations:


🏁 Script executed:

head -50 src/rotator_library/providers/kilocode_provider.py | cat -n

Repository: Mirrowel/LLM-API-Key-Proxy

Length of output: 1964


Catch HTTPStatusError here too.

response.raise_for_status() raises httpx.HTTPStatusError, not httpx.RequestError, so 4xx/5xx responses still escape this fallback and can abort model discovery instead of degrading to [].

Change the exception handler to catch httpx.HTTPError (parent of both HTTPStatusError and RequestError):

Suggested fix
-        except httpx.RequestError as e:
-            lib_logger.error(f"Failed to fetch Kilocode models: {e}")
+        except httpx.HTTPError as e:
+            lib_logger.error("Failed to fetch Kilocode models: %s", e)
             return []
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
response.raise_for_status()
return [
f"kilocode/{model['id']}" for model in response.json().get("data", [])
]
except httpx.RequestError as e:
lib_logger.error(f"Failed to fetch Kilocode models: {e}")
return []
response.raise_for_status()
return [
f"kilocode/{model['id']}" for model in response.json().get("data", [])
]
except httpx.HTTPError as e:
lib_logger.error("Failed to fetch Kilocode models: %s", e)
return []
🧰 Tools
🪛 Ruff (0.15.4)

[warning] 39-39: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


[warning] 39-39: Logging statement uses f-string

(G004)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rotator_library/providers/kilocode_provider.py` around lines 34 - 40, The
exception handler in the Kilocode model fetch block only catches
httpx.RequestError but response.raise_for_status() raises httpx.HTTPStatusError;
update the except clause in the function that calls response.raise_for_status()
(the Kilocode model discovery block in kilocode_provider.py) to catch
httpx.HTTPError (the parent of HTTPStatusError and RequestError) so that 4xx/5xx
responses are handled, keep the existing lib_logger.error(...) logging and
return [] fallback behavior.

Loading
Loading