agy 모델 카탈로그 탭 형식 파서 수정과 표준 경로 폴백
agy CLI가 2026-08부터 'agy models'를 'model_id<TAB>라벨' 형태로 출력하는데 _discover_agy_cli가 공백 포함 줄을 전부 버려 카탈로그가 비어 available=false가 됐다 (관리자 AI 운영 화면에서 'Agy가 선택 가능한 모델을 반환하지 않았습니다'). - 탭 줄은 왼쪽 토큰을 id로, CLI 라벨을 우선 적용. 탭 없는 공백 줄(상태 문구)은 계속 무시하고 구형 베어 id 출력 계약 유지 — 형식 이중 호환 - _binary가 PATH에 없어도 LOCALAPPDATA agy bin 표준 설치 경로를 폴백으로 탐색 - 검증: 신규 탭 형식 회귀 RED→GREEN, engine_gateway 63 passed, 실제 agy CLI로 15모델 live 조회 확인(default gemini-3.6-flash-high)
This commit is contained in:
parent
0d5e211bd5
commit
5afd92f8f3
2 changed files with 54 additions and 2 deletions
|
|
@ -109,6 +109,14 @@ def _binary(env_name: str, fallback: str) -> str | None:
|
|||
executable = shutil.which(f"{fallback}.exe")
|
||||
if executable:
|
||||
return executable
|
||||
if fallback == "agy":
|
||||
# agy 인스톨러의 Windows 표준 위치(%LOCALAPPDATA%\agy\bin). PATH에
|
||||
# 올라 있지 않은 머신에서도 게이트웨이가 공급자를 잃지 않게 한다.
|
||||
local_app_data = os.environ.get("LOCALAPPDATA", "")
|
||||
if local_app_data:
|
||||
well_known = Path(local_app_data) / "agy" / "bin" / "agy.exe"
|
||||
if well_known.exists():
|
||||
return str(well_known)
|
||||
return shim
|
||||
return shutil.which(fallback)
|
||||
|
||||
|
|
@ -356,6 +364,17 @@ async def _discover_agy_cli() -> EngineCapabilitiesResponse:
|
|||
|
||||
models: list[EngineModelOption] = []
|
||||
for line in stdout.splitlines():
|
||||
# agy CLI는 2026-08부터 "model_id\t표시 라벨" 형태로 출력한다. 탭 왼쪽 토큰이
|
||||
# 모델 id고 라벨은 CLI가 준 값을 우선한다. 탭 없이 공백이 섞인 줄은 상태/안내
|
||||
# 문구이므로 건너뛴다(구형 베어 id 출력 계약은 그대로 유지).
|
||||
label: str | None = None
|
||||
if "\t" in line:
|
||||
model_id, _, rest = line.partition("\t")
|
||||
model_id = model_id.strip()
|
||||
label = rest.strip() or None
|
||||
if not model_id:
|
||||
continue
|
||||
else:
|
||||
model_id = line.strip()
|
||||
if not model_id or any(char.isspace() for char in model_id):
|
||||
continue
|
||||
|
|
@ -369,7 +388,7 @@ async def _discover_agy_cli() -> EngineCapabilitiesResponse:
|
|||
models.append(
|
||||
EngineModelOption(
|
||||
id=model_id,
|
||||
label=_display_model_name(model_id),
|
||||
label=label or _display_model_name(model_id),
|
||||
description="Agy CLI가 현재 계정에 노출한 모델입니다.",
|
||||
reasoning_efforts=efforts,
|
||||
default_reasoning_effort=default_effort,
|
||||
|
|
|
|||
|
|
@ -117,6 +117,39 @@ class ProviderRegistryTest(unittest.IsolatedAsyncioTestCase):
|
|||
self.assertEqual(selected.reasoning_efforts, ["high"])
|
||||
self.assertEqual(selected.label, "Gemini 3.6 Flash (High)")
|
||||
|
||||
async def test_agy_catalog_parses_tab_separated_id_label_lines(self):
|
||||
# 2026-08 agy CLI는 `agy models`를 "model_id\t표시 라벨" 형태로 출력한다.
|
||||
# 탭이 포함된 줄을 통째로 버리면 카탈로그가 비어 available=false가 된다.
|
||||
stdout = "\n".join(
|
||||
[
|
||||
"gemini-3.7-flash-high\tGemini 3.7 Flash (High)",
|
||||
"gemini-3.7-flash-medium\tGemini 3.7 Flash (Medium)",
|
||||
"claude-sonnet-4-6\tClaude Sonnet 4.6 (Thinking)",
|
||||
"Fetching available models...",
|
||||
]
|
||||
)
|
||||
with (
|
||||
patch.object(provider_registry, "_binary", return_value="agy.exe"),
|
||||
patch.object(
|
||||
provider_registry,
|
||||
"_run_process",
|
||||
AsyncMock(return_value=(stdout, "")),
|
||||
),
|
||||
):
|
||||
result = await provider_registry.discover_capabilities("agy_cli")
|
||||
|
||||
self.assertTrue(result.available)
|
||||
self.assertEqual(
|
||||
[model.id for model in result.models],
|
||||
["gemini-3.7-flash-high", "gemini-3.7-flash-medium", "claude-sonnet-4-6"],
|
||||
)
|
||||
# 기본 모델 gemini-3.6-flash-high 가 목록에 없으면 첫 모델로 폴백한다.
|
||||
self.assertEqual(result.default_model, "gemini-3.7-flash-high")
|
||||
self.assertEqual(result.default_reasoning_effort, "high")
|
||||
selected = next(model for model in result.models if model.id == result.default_model)
|
||||
self.assertEqual(selected.reasoning_efforts, ["high"])
|
||||
self.assertEqual(selected.label, "Gemini 3.7 Flash (High)")
|
||||
|
||||
async def test_claude_cli_catalog_is_explicit_static_alias_fallback(self):
|
||||
with patch.object(provider_registry, "_binary", return_value="claude.exe"):
|
||||
result = await provider_registry.discover_capabilities("claude_cli")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue