Skip to content

fix(providers): accept camelCase aliases and validate URL scheme in providers: config (#9332)#9373

Closed
draix wants to merge 1 commit into
NousResearch:mainfrom
draix:fix/9332-providers-config-camelcase-validation
Closed

fix(providers): accept camelCase aliases and validate URL scheme in providers: config (#9332)#9373
draix wants to merge 1 commit into
NousResearch:mainfrom
draix:fix/9332-providers-config-camelcase-validation

Conversation

@draix

@draix draix commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Two bugs in resolve_user_provider() (hermes_cli/providers.py) caused silent misconfigurations in the providers: keyed config section.

Bug 1: camelCase keys silently dropped

Hand-written YAML often uses camelCase — especially by developers coming from JavaScript/TypeScript environments. baseUrl, apiKey, keyEnv were silently dropped, leaving base_url empty and api_key_env_vars empty.

User-visible symptom: Bearer no-key-required in outgoing requests and cryptic APIConnectionError with no hint that the config key is wrong.

Fix: Add camelCase aliases with snake_case taking precedence:

# All of these now work:
providers:
  myproxy:
    base_url: https://proxy.example.com/v1   # snake_case (preferred)
    baseUrl: https://proxy.example.com/v1    # camelCase alias (also accepted)
    key_env: MY_PROXY_KEY                    # snake_case (preferred)
    keyEnv: MY_PROXY_KEY                     # camelCase alias (also accepted)

Resolution order (first non-empty wins): base_url > baseUrl > url > api

Bug 2: Non-URL strings silently accepted as endpoints

The api field lookup accepted the first non-empty string regardless of whether it looks like a URL. A bare string like openai-reverse-proxy was silently accepted as the endpoint, causing confusing downstream connection failures.

Fix: After resolving the URL candidate, check for http:// or https:// prefix. If the string doesn't have a recognisable scheme, emit a WARNING with the offending value and the accepted key names, and clear the field.

WARNING providers[myprovider]: base_url value 'openai-reverse-proxy' does not look like a URL
(missing http:// or https:// scheme) — check your config.yaml.
Accepted keys: base_url, baseUrl, url, api.

Testing

20 new tests in tests/hermes_cli/test_providers_config_parsing.py:

Class Tests Coverage
TestSnakeCaseKeys 4 Existing behavior preserved (regression guard)
TestCamelCaseAliases 5 baseUrl, keyEnv, both together, precedence order
TestURLValidation 5 Non-URL rejection + warning, valid http/https accepted, empty no-warn
TestEdgeCases 6 None entry, empty dict, missing key, name field, transport

All 20 pass.

Fixes #9332

…roviders: config (NousResearch#9332)

Two bugs in resolve_user_provider() caused silent misconfigurations:

1. Only snake_case keys recognised
   Hand-written YAML often uses camelCase — especially by developers
   coming from JavaScript/TypeScript environments.  'baseUrl', 'keyEnv'
   were silently dropped, leaving base_url empty and api_key_env_vars
   empty, producing 'Bearer no-key-required' errors with no hint about
   the actual cause.

   Fix: add camelCase aliases resolved after the primary snake_case keys.
   Resolution order (first non-empty wins):
     base_url  > baseUrl  > url  > api    (URL field)
     key_env   > keyEnv   > api_key_env   (env-var field)
   snake_case always takes precedence when both forms are present.

2. Non-URL strings silently accepted as endpoint URLs
   The 'api' field lookup accepts the first non-empty string regardless
   of whether it looks like a URL.  A bare string like
   'openai-reverse-proxy' was silently accepted as the endpoint, causing
   cryptic downstream connection errors.

   Fix: after resolving the URL candidate, check for http:// or https://
   prefix.  If the string doesn't have a recognisable scheme, log a
   WARNING with the offending value and the accepted key names, and clear
   the field so the provider falls back to its built-in defaults.

Testing
-------
20 new tests in tests/hermes_cli/test_providers_config_parsing.py:
- TestSnakeCaseKeys (4): existing behavior preserved
- TestCamelCaseAliases (5): baseUrl, keyEnv, both together, precedence
- TestURLValidation (5): non-URL rejection + warning, valid URLs accepted
- TestEdgeCases (6): None entry, empty dict, missing key, name, transport

Fixes NousResearch#9332
@teknium1

Copy link
Copy Markdown
Contributor

Closed in favor of PR #12993 #12993 which salvaged #9359's implementation. Your fix was solid — the camelCase alias approach and URL validation were correct. Thanks @draix!

@teknium1 teknium1 closed this Apr 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: providers: config entries silently ignore apiKey/baseUrl and accept non-URL strings as base_url via the api field

3 participants