fix(copilot): pass two-slash custom-model ids through unchanged
Copilot enterprise custom models (BYOK) expose catalog ids shaped owner/sub/model. normalize_copilot_model_id() only tried the full id and the id minus its FIRST segment, and returned the stripped form when neither matched the (unreachable) catalog - corrupting those ids into sub/model, which the CLI's second normalization pass then stripped again to model. The Copilot API answered HTTP 400 model_not_supported on every call. Only accept the strip guess when the remainder is itself a flat id: a result that still contains "/" cannot be a Copilot id. Fixes #110597
This commit is contained in:
@@ -2051,7 +2051,12 @@ def normalize_copilot_model_id(
|
||||
return candidate
|
||||
|
||||
if "/" in raw:
|
||||
return raw.split("/", 1)[1].strip()
|
||||
stripped = raw.split("/", 1)[1].strip()
|
||||
# Enterprise BYOK custom models expose ``owner/sub/model`` ids (two
|
||||
# slashes). A strip guess that still contains "/" cannot be a Copilot
|
||||
# id, so pass the input through untouched instead of corrupting it.
|
||||
if stripped and "/" not in stripped:
|
||||
return stripped
|
||||
return raw
|
||||
|
||||
|
||||
|
||||
37
tests/hermes_cli/test_copilot_custom_model_ids.py
Normal file
37
tests/hermes_cli/test_copilot_custom_model_ids.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""Regression for #110597: Copilot custom/enterprise (BYOK) model ids must survive normalization.
|
||||
|
||||
GitHub's enterprise custom-model (BYOK) catalog exposes Copilot ids shaped ``owner/sub/model``
|
||||
— that is, TWO slashes. With no candidate matching the (unreachable) catalog,
|
||||
``normalize_copilot_model_id`` stripped the FIRST path segment and returned the result:
|
||||
``acme-github-copilot/GLM/glm-5.2`` became ``GLM/glm-5.2``, the CLI's second normalization pass
|
||||
stripped again to ``glm-5.2``, and the Copilot API answered HTTP 400 ``model_not_supported``.
|
||||
Every such call failed, for both custom models a tenant had configured.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from hermes_cli.models import normalize_copilot_model_id
|
||||
|
||||
|
||||
# ``owner/sub/model`` — the shape an enterprise BYOK custom model has (tenant name sanitized).
|
||||
CUSTOM_MODEL_IDS = [
|
||||
"acme-github-copilot/QDeepseekV4/deepseek-flash",
|
||||
"acme-github-copilot/GLM/glm-5.2",
|
||||
"acme-github-copilot/HUAWEI/glm-5.2",
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model_id", CUSTOM_MODEL_IDS)
|
||||
def test_two_slash_custom_model_id_is_passed_through(model_id):
|
||||
"""A two-slash id IS a Copilot id — normalize nothing, catalog or no catalog."""
|
||||
# ``hermes_cli/model_normalize.py`` calls this without an api_key, so the catalog is always
|
||||
# empty on the runtime path that produced the 400.
|
||||
assert normalize_copilot_model_id(model_id, catalog=[], api_key=None) == model_id
|
||||
assert normalize_copilot_model_id(
|
||||
model_id, catalog=[{"id": model_id}], api_key=None) == model_id
|
||||
|
||||
|
||||
def test_single_vendor_prefix_still_folds_to_the_bare_id():
|
||||
"""Contract preserved for the one-slash case this stripping exists for (#6879)."""
|
||||
assert normalize_copilot_model_id(
|
||||
"anthropic/claude-sonnet-4.6", catalog=[], api_key=None) == "claude-sonnet-4.6"
|
||||
Reference in New Issue
Block a user