feat(wizard): auto-detect Literal fields as select menus
Literal["standard", "persistent"] fields are now rendered as select dropdowns instead of free-text input. This makes provider_retry_mode and any future Literal fields self-documenting in the wizard.
This commit is contained in:
+12
-1
@@ -4,7 +4,7 @@ import json
|
|||||||
import types
|
import types
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from typing import Any, NamedTuple, get_args, get_origin
|
from typing import Any, Literal, NamedTuple, get_args, get_origin
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import questionary
|
import questionary
|
||||||
@@ -202,6 +202,8 @@ def _get_field_type_info(field_info) -> FieldTypeInfo:
|
|||||||
return FieldTypeInfo(name, None)
|
return FieldTypeInfo(name, None)
|
||||||
if isinstance(annotation, type) and issubclass(annotation, BaseModel):
|
if isinstance(annotation, type) and issubclass(annotation, BaseModel):
|
||||||
return FieldTypeInfo("model", annotation)
|
return FieldTypeInfo("model", annotation)
|
||||||
|
if origin is Literal:
|
||||||
|
return FieldTypeInfo("literal", list(args))
|
||||||
return FieldTypeInfo("str", None)
|
return FieldTypeInfo("str", None)
|
||||||
|
|
||||||
|
|
||||||
@@ -681,6 +683,15 @@ def _configure_pydantic_model(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Generic field input
|
# Generic field input
|
||||||
|
if ftype.type_name == "literal" and ftype.inner_type:
|
||||||
|
select_choices = [str(v) for v in ftype.inner_type]
|
||||||
|
default_choice = str(current_value) if current_value in ftype.inner_type else select_choices[0]
|
||||||
|
new_value = _select_with_back(field_display, select_choices, default=default_choice)
|
||||||
|
if new_value is _BACK_PRESSED:
|
||||||
|
continue
|
||||||
|
if new_value is not None:
|
||||||
|
setattr(working_model, field_name, new_value)
|
||||||
|
continue
|
||||||
if ftype.type_name == "bool":
|
if ftype.type_name == "bool":
|
||||||
new_value = _input_bool(field_display, current_value)
|
new_value = _input_bool(field_display, current_value)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -210,6 +210,24 @@ class TestGetFieldTypeInfo:
|
|||||||
assert type_name == "str"
|
assert type_name == "str"
|
||||||
assert inner is None
|
assert inner is None
|
||||||
|
|
||||||
|
def test_literal_type_returns_literal_with_choices(self):
|
||||||
|
"""Literal["a", "b"] should return ("literal", ["a", "b"])."""
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
class Model(BaseModel):
|
||||||
|
mode: Literal["standard", "persistent"] = "standard"
|
||||||
|
|
||||||
|
type_name, inner = _get_field_type_info(Model.model_fields["mode"])
|
||||||
|
assert type_name == "literal"
|
||||||
|
assert inner == ["standard", "persistent"]
|
||||||
|
|
||||||
|
def test_real_provider_retry_mode_field(self):
|
||||||
|
"""Validate against actual AgentDefaults.provider_retry_mode field."""
|
||||||
|
from nanobot.config.schema import AgentDefaults
|
||||||
|
|
||||||
|
type_name, inner = _get_field_type_info(AgentDefaults.model_fields["provider_retry_mode"])
|
||||||
|
assert type_name == "literal"
|
||||||
|
assert inner == ["standard", "persistent"]
|
||||||
|
|
||||||
|
|
||||||
class TestGetFieldDisplayName:
|
class TestGetFieldDisplayName:
|
||||||
|
|||||||
Reference in New Issue
Block a user