alloy-runtime-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- alloy_runtime_cli-0.1.0.dist-info/METADATA +61 -0
- alloy_runtime_cli-0.1.0.dist-info/RECORD +451 -0
- alloy_runtime_cli-0.1.0.dist-info/WHEEL +5 -0
- alloy_runtime_cli-0.1.0.dist-info/entry_points.txt +2 -0
- alloy_runtime_cli-0.1.0.dist-info/top_level.txt +1 -0
- cli/__init__.py +0 -0
- cli/commands/__init__.py +0 -0
- cli/commands/admin/__init__.py +0 -0
- cli/commands/admin/bootstrap_command.py +118 -0
- cli/commands/admin/credentials/__init__.py +0 -0
- cli/commands/admin/credentials/create/__init__.py +0 -0
- cli/commands/admin/credentials/create/command.py +148 -0
- cli/commands/admin/credentials/create/presenter.py +16 -0
- cli/commands/admin/credentials/grant/__init__.py +0 -0
- cli/commands/admin/credentials/grant/command.py +119 -0
- cli/commands/admin/credentials/grant/fields.py +33 -0
- cli/commands/admin/credentials/grant/presenter.py +23 -0
- cli/commands/agents/__init__.py +0 -0
- cli/commands/agents/create/__init__.py +0 -0
- cli/commands/agents/create/command.py +475 -0
- cli/commands/agents/create/fields.py +64 -0
- cli/commands/agents/create/presenter.py +68 -0
- cli/commands/agents/delete/__init__.py +0 -0
- cli/commands/agents/delete/command.py +47 -0
- cli/commands/agents/delete/presenter.py +16 -0
- cli/commands/agents/get/command.py +37 -0
- cli/commands/agents/get/presenter.py +32 -0
- cli/commands/agents/list/__init__.py +1 -0
- cli/commands/agents/list/command.py +54 -0
- cli/commands/agents/list/presenter.py +82 -0
- cli/commands/agents/update/__init__.py +0 -0
- cli/commands/agents/update/command.py +435 -0
- cli/commands/agents/update/fields.py +40 -0
- cli/commands/agents/update/presenter.py +68 -0
- cli/commands/audio/__init__.py +0 -0
- cli/commands/audio/transcribe/__init__.py +0 -0
- cli/commands/audio/transcribe/command.py +144 -0
- cli/commands/audio/transcribe/presenter.py +15 -0
- cli/commands/auth/__init__.py +0 -0
- cli/commands/auth/login/__init__.py +0 -0
- cli/commands/auth/login/command.py +80 -0
- cli/commands/auth/signup/__init__.py +0 -0
- cli/commands/auth/signup/command.py +115 -0
- cli/commands/billing/__init__.py +1 -0
- cli/commands/billing/costs/__init__.py +1 -0
- cli/commands/billing/costs/by_agent/__init__.py +1 -0
- cli/commands/billing/costs/by_agent/command.py +57 -0
- cli/commands/billing/costs/by_agent/presenter.py +81 -0
- cli/commands/billing/costs/by_model/__init__.py +1 -0
- cli/commands/billing/costs/by_model/command.py +57 -0
- cli/commands/billing/costs/by_model/presenter.py +80 -0
- cli/commands/billing/costs/daily/__init__.py +1 -0
- cli/commands/billing/costs/daily/command.py +55 -0
- cli/commands/billing/costs/daily/presenter.py +75 -0
- cli/commands/billing/costs/summary/__init__.py +1 -0
- cli/commands/billing/costs/summary/command.py +57 -0
- cli/commands/billing/costs/summary/presenter.py +42 -0
- cli/commands/billing/projects/__init__.py +1 -0
- cli/commands/billing/projects/create/__init__.py +1 -0
- cli/commands/billing/projects/create/command.py +60 -0
- cli/commands/billing/projects/create/presenter.py +26 -0
- cli/commands/billing/projects/get/__init__.py +1 -0
- cli/commands/billing/projects/get/command.py +33 -0
- cli/commands/billing/projects/get/presenter.py +32 -0
- cli/commands/billing/projects/list/__init__.py +1 -0
- cli/commands/billing/projects/list/command.py +40 -0
- cli/commands/billing/projects/list/presenter.py +57 -0
- cli/commands/content/__init__.py +1 -0
- cli/commands/content/delete/__init__.py +0 -0
- cli/commands/content/delete/command.py +49 -0
- cli/commands/content/delete/presenter.py +18 -0
- cli/commands/content/edit/__init__.py +1 -0
- cli/commands/content/edit/command.py +155 -0
- cli/commands/content/edit/editor.py +150 -0
- cli/commands/content/edit/presenter.py +146 -0
- cli/commands/content/get/__init__.py +1 -0
- cli/commands/content/get/command.py +39 -0
- cli/commands/content/get/presenter.py +176 -0
- cli/commands/content/list/__init__.py +1 -0
- cli/commands/content/list/command.py +347 -0
- cli/commands/content/list/export_formatters.py +409 -0
- cli/commands/content/list/export_handler.py +165 -0
- cli/commands/content/list/presenter.py +190 -0
- cli/commands/credentials/__init__.py +0 -0
- cli/commands/credentials/create/__init__.py +0 -0
- cli/commands/credentials/create/command.py +165 -0
- cli/commands/credentials/create/fields.py +38 -0
- cli/commands/credentials/create/presenter.py +20 -0
- cli/commands/credentials/update/__init__.py +0 -0
- cli/commands/credentials/update/command.py +53 -0
- cli/commands/credentials/update/fields.py +71 -0
- cli/commands/credentials/update/presenter.py +16 -0
- cli/commands/flag_utils.py +366 -0
- cli/commands/generate/__init__.py +0 -0
- cli/commands/generate/cancel/__init__.py +1 -0
- cli/commands/generate/cancel/command.py +44 -0
- cli/commands/generate/cancel/presenter.py +26 -0
- cli/commands/generate/status/__init__.py +1 -0
- cli/commands/generate/status/command.py +58 -0
- cli/commands/generate/status/presenter.py +78 -0
- cli/commands/generate/text/__init__.py +0 -0
- cli/commands/generate/text/command.py +1325 -0
- cli/commands/generate/text/concurrent_renderer.py +355 -0
- cli/commands/generate/text/presenter.py +287 -0
- cli/commands/generate/text/stream_renderer.py +129 -0
- cli/commands/knowledge/__init__.py +0 -0
- cli/commands/knowledge/collections/__init__.py +0 -0
- cli/commands/knowledge/collections/cluster/__init__.py +0 -0
- cli/commands/knowledge/collections/cluster/command.py +64 -0
- cli/commands/knowledge/collections/cluster/presenter.py +74 -0
- cli/commands/knowledge/collections/cluster_status/__init__.py +0 -0
- cli/commands/knowledge/collections/cluster_status/command.py +46 -0
- cli/commands/knowledge/collections/cluster_status/presenter.py +10 -0
- cli/commands/knowledge/collections/create/__init__.py +0 -0
- cli/commands/knowledge/collections/create/command.py +137 -0
- cli/commands/knowledge/collections/create/presenter.py +38 -0
- cli/commands/knowledge/collections/delete/__init__.py +1 -0
- cli/commands/knowledge/collections/delete/command.py +47 -0
- cli/commands/knowledge/collections/delete/presenter.py +20 -0
- cli/commands/knowledge/collections/get/__init__.py +1 -0
- cli/commands/knowledge/collections/get/command.py +30 -0
- cli/commands/knowledge/collections/get/presenter.py +44 -0
- cli/commands/knowledge/collections/list/__init__.py +1 -0
- cli/commands/knowledge/collections/list/command.py +41 -0
- cli/commands/knowledge/collections/list/presenter.py +68 -0
- cli/commands/knowledge/collections/update/__init__.py +0 -0
- cli/commands/knowledge/collections/update/command.py +97 -0
- cli/commands/knowledge/collections/update/presenter.py +42 -0
- cli/commands/knowledge/documents/__init__.py +0 -0
- cli/commands/knowledge/documents/bulk_metadata/__init__.py +0 -0
- cli/commands/knowledge/documents/bulk_metadata/command.py +119 -0
- cli/commands/knowledge/documents/bulk_metadata/presenter.py +36 -0
- cli/commands/knowledge/documents/delete/__init__.py +0 -0
- cli/commands/knowledge/documents/delete/command.py +47 -0
- cli/commands/knowledge/documents/delete/presenter.py +20 -0
- cli/commands/knowledge/documents/get/__init__.py +0 -0
- cli/commands/knowledge/documents/get/command.py +39 -0
- cli/commands/knowledge/documents/get/presenter.py +78 -0
- cli/commands/knowledge/documents/ingest/__init__.py +0 -0
- cli/commands/knowledge/documents/ingest/command.py +222 -0
- cli/commands/knowledge/documents/ingest/presenter.py +41 -0
- cli/commands/knowledge/documents/list/__init__.py +0 -0
- cli/commands/knowledge/documents/list/command.py +69 -0
- cli/commands/knowledge/documents/list/presenter.py +86 -0
- cli/commands/knowledge/documents/reingest/__init__.py +0 -0
- cli/commands/knowledge/documents/reingest/command.py +102 -0
- cli/commands/knowledge/documents/reingest/presenter.py +70 -0
- cli/commands/knowledge/documents/update/__init__.py +0 -0
- cli/commands/knowledge/documents/update/command.py +85 -0
- cli/commands/knowledge/documents/update/presenter.py +37 -0
- cli/commands/knowledge/recover/__init__.py +0 -0
- cli/commands/knowledge/recover/command.py +46 -0
- cli/commands/knowledge/recover/presenter.py +79 -0
- cli/commands/knowledge/search/__init__.py +0 -0
- cli/commands/knowledge/search/command.py +218 -0
- cli/commands/knowledge/search/presenter.py +111 -0
- cli/commands/knowledge/synthesis/__init__.py +0 -0
- cli/commands/knowledge/synthesis/create/__init__.py +0 -0
- cli/commands/knowledge/synthesis/create/command.py +127 -0
- cli/commands/knowledge/synthesis/create/presenter.py +33 -0
- cli/commands/knowledge/synthesis/delete/__init__.py +0 -0
- cli/commands/knowledge/synthesis/delete/command.py +53 -0
- cli/commands/knowledge/synthesis/delete/presenter.py +31 -0
- cli/commands/knowledge/synthesis/get/__init__.py +0 -0
- cli/commands/knowledge/synthesis/get/command.py +55 -0
- cli/commands/knowledge/synthesis/get/presenter.py +114 -0
- cli/commands/knowledge/synthesis/list/__init__.py +0 -0
- cli/commands/knowledge/synthesis/list/command.py +132 -0
- cli/commands/knowledge/synthesis/list/presenter.py +84 -0
- cli/commands/knowledge/synthesis/refresh/__init__.py +0 -0
- cli/commands/knowledge/synthesis/refresh/command.py +42 -0
- cli/commands/knowledge/synthesis/refresh/presenter.py +33 -0
- cli/commands/knowledge/synthesis/update/__init__.py +0 -0
- cli/commands/knowledge/synthesis/update/command.py +76 -0
- cli/commands/knowledge/synthesis/update/presenter.py +41 -0
- cli/commands/models/__init__.py +0 -0
- cli/commands/models/list/__init__.py +0 -0
- cli/commands/models/list/command.py +84 -0
- cli/commands/models/list/presenter.py +114 -0
- cli/commands/organizations/__init__.py +0 -0
- cli/commands/organizations/create/command.py +32 -0
- cli/commands/organizations/create/presenter.py +9 -0
- cli/commands/pipelines/__init__.py +1 -0
- cli/commands/pipelines/approvals/__init__.py +1 -0
- cli/commands/pipelines/approvals/decide_command.py +77 -0
- cli/commands/pipelines/approvals/get_command.py +44 -0
- cli/commands/pipelines/approvals/presenter.py +56 -0
- cli/commands/pipelines/costs/__init__.py +1 -0
- cli/commands/pipelines/costs/command.py +57 -0
- cli/commands/pipelines/costs/daily_command.py +54 -0
- cli/commands/pipelines/costs/daily_presenter.py +59 -0
- cli/commands/pipelines/costs/presenter.py +37 -0
- cli/commands/pipelines/create/__init__.py +1 -0
- cli/commands/pipelines/create/command.py +103 -0
- cli/commands/pipelines/create/presenter.py +22 -0
- cli/commands/pipelines/env_vars/__init__.py +1 -0
- cli/commands/pipelines/env_vars/command.py +51 -0
- cli/commands/pipelines/env_vars/presenter.py +16 -0
- cli/commands/pipelines/execute/__init__.py +1 -0
- cli/commands/pipelines/execute/command.py +142 -0
- cli/commands/pipelines/execute/presenter.py +47 -0
- cli/commands/pipelines/executions/__init__.py +1 -0
- cli/commands/pipelines/executions/costs/__init__.py +1 -0
- cli/commands/pipelines/executions/costs/command.py +48 -0
- cli/commands/pipelines/executions/costs/presenter.py +29 -0
- cli/commands/pipelines/executions/costs_by_model/__init__.py +1 -0
- cli/commands/pipelines/executions/costs_by_model/command.py +50 -0
- cli/commands/pipelines/executions/costs_by_model/presenter.py +78 -0
- cli/commands/pipelines/executions/costs_by_step/__init__.py +1 -0
- cli/commands/pipelines/executions/costs_by_step/command.py +50 -0
- cli/commands/pipelines/executions/costs_by_step/presenter.py +72 -0
- cli/commands/pipelines/executions/get_command.py +38 -0
- cli/commands/pipelines/executions/list_command.py +123 -0
- cli/commands/pipelines/executions/presenter.py +131 -0
- cli/commands/pipelines/executions/rerun_command.py +41 -0
- cli/commands/pipelines/executions/update/__init__.py +1 -0
- cli/commands/pipelines/executions/update/command.py +110 -0
- cli/commands/pipelines/executions/update/presenter.py +28 -0
- cli/commands/pipelines/get/__init__.py +1 -0
- cli/commands/pipelines/get/command.py +33 -0
- cli/commands/pipelines/get/presenter.py +48 -0
- cli/commands/pipelines/list/__init__.py +1 -0
- cli/commands/pipelines/list/command.py +53 -0
- cli/commands/pipelines/list/presenter.py +66 -0
- cli/commands/pipelines/schedules/__init__.py +1 -0
- cli/commands/pipelines/schedules/create_command.py +119 -0
- cli/commands/pipelines/schedules/create_presenter.py +35 -0
- cli/commands/pipelines/schedules/delete_command.py +52 -0
- cli/commands/pipelines/schedules/env_vars_command.py +59 -0
- cli/commands/pipelines/schedules/env_vars_presenter.py +16 -0
- cli/commands/pipelines/schedules/get_command.py +38 -0
- cli/commands/pipelines/schedules/list_command.py +33 -0
- cli/commands/pipelines/schedules/once_command.py +90 -0
- cli/commands/pipelines/schedules/once_presenter.py +30 -0
- cli/commands/pipelines/schedules/presenter.py +104 -0
- cli/commands/pipelines/schedules/update_command.py +139 -0
- cli/commands/pipelines/schedules/update_presenter.py +29 -0
- cli/commands/render/__init__.py +0 -0
- cli/commands/render/html_to_image/__init__.py +0 -0
- cli/commands/render/html_to_image/command.py +170 -0
- cli/commands/schemas/__init__.py +0 -0
- cli/commands/schemas/create/__init__.py +0 -0
- cli/commands/schemas/create/command.py +122 -0
- cli/commands/schemas/create/presenter.py +53 -0
- cli/commands/schemas/delete/command.py +45 -0
- cli/commands/schemas/delete/presenter.py +9 -0
- cli/commands/schemas/get/__init__.py +0 -0
- cli/commands/schemas/get/command.py +56 -0
- cli/commands/schemas/get/presenter.py +129 -0
- cli/commands/schemas/list/__init__.py +0 -0
- cli/commands/schemas/list/command.py +64 -0
- cli/commands/schemas/list/presenter.py +133 -0
- cli/commands/schemas/update/__init__.py +0 -0
- cli/commands/schemas/update/command.py +369 -0
- cli/commands/schemas/update/presenter.py +53 -0
- cli/commands/sessions/__init__.py +1 -0
- cli/commands/sessions/delete/__init__.py +1 -0
- cli/commands/sessions/delete/command.py +47 -0
- cli/commands/sessions/delete/presenter.py +10 -0
- cli/commands/sessions/get/__init__.py +1 -0
- cli/commands/sessions/get/command.py +42 -0
- cli/commands/sessions/get/presenter.py +59 -0
- cli/commands/sessions/list/__init__.py +1 -0
- cli/commands/sessions/list/command.py +61 -0
- cli/commands/sessions/list/presenter.py +68 -0
- cli/commands/sessions/messages/__init__.py +1 -0
- cli/commands/sessions/messages/command.py +78 -0
- cli/commands/sessions/messages/presenter.py +79 -0
- cli/commands/shared_flags.py +500 -0
- cli/commands/sync/__init__.py +0 -0
- cli/commands/sync/command.py +45 -0
- cli/commands/sync/presenter.py +49 -0
- cli/commands/tags/__init__.py +1 -0
- cli/commands/tags/create/__init__.py +1 -0
- cli/commands/tags/create/command.py +60 -0
- cli/commands/tags/delete/__init__.py +1 -0
- cli/commands/tags/delete/command.py +47 -0
- cli/commands/tags/delete/presenter.py +10 -0
- cli/commands/tags/get/command.py +31 -0
- cli/commands/tags/get/presenter.py +23 -0
- cli/commands/tags/list/__init__.py +1 -0
- cli/commands/tags/list/command.py +52 -0
- cli/commands/tags/list/presenter.py +49 -0
- cli/commands/tags/update/command.py +64 -0
- cli/commands/tags/update/presenter.py +9 -0
- cli/commands/templates/__init__.py +0 -0
- cli/commands/templates/create/__init__.py +0 -0
- cli/commands/templates/create/command.py +152 -0
- cli/commands/templates/create/presenter.py +86 -0
- cli/commands/templates/delete/__init__.py +0 -0
- cli/commands/templates/delete/command.py +47 -0
- cli/commands/templates/delete/presenter.py +16 -0
- cli/commands/templates/get/__init__.py +0 -0
- cli/commands/templates/get/command.py +52 -0
- cli/commands/templates/get/presenter.py +233 -0
- cli/commands/templates/get_by_version/command.py +32 -0
- cli/commands/templates/get_by_version/presenter.py +30 -0
- cli/commands/templates/list/__init__.py +1 -0
- cli/commands/templates/list/command.py +102 -0
- cli/commands/templates/list/presenter.py +93 -0
- cli/commands/templates/render/__init__.py +0 -0
- cli/commands/templates/render/command.py +115 -0
- cli/commands/templates/render/presenter.py +276 -0
- cli/commands/templates/update/__init__.py +0 -0
- cli/commands/templates/update/command.py +199 -0
- cli/commands/templates/update/presenter.py +94 -0
- cli/commands/templates/version/__init__.py +1 -0
- cli/commands/templates/version/command.py +116 -0
- cli/commands/templates/version/presenter.py +100 -0
- cli/commands/tool_configs/__init__.py +0 -0
- cli/commands/tool_configs/create/__init__.py +0 -0
- cli/commands/tool_configs/create/command.py +118 -0
- cli/commands/tool_configs/create/presenter.py +53 -0
- cli/commands/tool_configs/delete/__init__.py +0 -0
- cli/commands/tool_configs/delete/command.py +47 -0
- cli/commands/tool_configs/delete/presenter.py +18 -0
- cli/commands/tool_configs/get/__init__.py +0 -0
- cli/commands/tool_configs/get/command.py +31 -0
- cli/commands/tool_configs/get/presenter.py +62 -0
- cli/commands/tool_configs/list/__init__.py +0 -0
- cli/commands/tool_configs/list/command.py +59 -0
- cli/commands/tool_configs/list/presenter.py +60 -0
- cli/commands/tool_configs/update/__init__.py +0 -0
- cli/commands/tool_configs/update/command.py +128 -0
- cli/commands/tool_configs/update/presenter.py +53 -0
- cli/commands/tools/__init__.py +1 -0
- cli/commands/tools/get/__init__.py +1 -0
- cli/commands/tools/get/command.py +42 -0
- cli/commands/tools/get/presenter.py +45 -0
- cli/commands/tools/list/__init__.py +1 -0
- cli/commands/tools/list/command.py +56 -0
- cli/commands/tools/list/presenter.py +44 -0
- cli/commands/users/__init__.py +0 -0
- cli/commands/users/create/command.py +53 -0
- cli/commands/users/create/presenter.py +9 -0
- cli/commands/whoami/__init__.py +0 -0
- cli/commands/whoami/command.py +42 -0
- cli/infrastructure/__init__.py +0 -0
- cli/infrastructure/auth_storage.py +71 -0
- cli/infrastructure/client_factory.py +36 -0
- cli/infrastructure/command.py +75 -0
- cli/infrastructure/config.py +188 -0
- cli/infrastructure/console.py +27 -0
- cli/infrastructure/editor.py +138 -0
- cli/infrastructure/error_display.py +178 -0
- cli/infrastructure/field_extractor.py +360 -0
- cli/infrastructure/file_content.py +210 -0
- cli/infrastructure/filter_parser.py +256 -0
- cli/infrastructure/formatters/__init__.py +0 -0
- cli/infrastructure/formatters/base.py +99 -0
- cli/infrastructure/formatters/compact_formatter.py +245 -0
- cli/infrastructure/formatters/json_formatter.py +84 -0
- cli/infrastructure/formatters/lines_formatter.py +102 -0
- cli/infrastructure/formatting/__init__.py +0 -0
- cli/infrastructure/formatting/fields.py +193 -0
- cli/infrastructure/forms/__init__.py +0 -0
- cli/infrastructure/forms/agent_picker.py +123 -0
- cli/infrastructure/forms/agent_tool_editor.py +384 -0
- cli/infrastructure/forms/agent_tools_manager.py +212 -0
- cli/infrastructure/forms/base_picker.py +469 -0
- cli/infrastructure/forms/components.py +126 -0
- cli/infrastructure/forms/json_schema_builder.py +149 -0
- cli/infrastructure/forms/model_picker.py +134 -0
- cli/infrastructure/forms/parsers.py +173 -0
- cli/infrastructure/forms/resolution_modal.py +302 -0
- cli/infrastructure/forms/schema_picker.py +137 -0
- cli/infrastructure/forms/tag_management_modal.py +103 -0
- cli/infrastructure/forms/tag_picker.py +207 -0
- cli/infrastructure/forms/template_picker.py +131 -0
- cli/infrastructure/forms/tool_config_picker.py +130 -0
- cli/infrastructure/forms/tool_picker.py +103 -0
- cli/infrastructure/injection/__init__.py +0 -0
- cli/infrastructure/injection/parser.py +302 -0
- cli/infrastructure/injection/resolver.py +399 -0
- cli/infrastructure/kv_parser.py +130 -0
- cli/infrastructure/local_storage.py +227 -0
- cli/infrastructure/macro_parser.py +215 -0
- cli/infrastructure/output.py +192 -0
- cli/infrastructure/provider_setup.py +81 -0
- cli/infrastructure/renderers/__init__.py +0 -0
- cli/infrastructure/renderers/entity_renderer.py +77 -0
- cli/infrastructure/renderers/list_renderer.py +114 -0
- cli/infrastructure/scope_utils.py +47 -0
- cli/infrastructure/spinner.py +101 -0
- cli/infrastructure/tui/__init__.py +0 -0
- cli/infrastructure/tui/clipboard.py +41 -0
- cli/infrastructure/tui/formatters.py +105 -0
- cli/infrastructure/tui/preview.py +14 -0
- cli/infrastructure/tui/selectable.py +198 -0
- cli/infrastructure/validation/__init__.py +0 -0
- cli/infrastructure/validation/tag_validation.py +74 -0
- cli/main.py +759 -0
- cli/tui/__init__.py +0 -0
- cli/tui/app.py +199 -0
- cli/tui/app_store.py +73 -0
- cli/tui/chat/__init__.py +0 -0
- cli/tui/chat/commands/__init__.py +0 -0
- cli/tui/chat/commands/base.py +65 -0
- cli/tui/chat/commands/create_session.py +135 -0
- cli/tui/chat/commands/load_session.py +119 -0
- cli/tui/chat/commands/regenerate.py +120 -0
- cli/tui/chat/commands/reload_session.py +63 -0
- cli/tui/chat/commands/send_message.py +190 -0
- cli/tui/chat/commands/undo.py +66 -0
- cli/tui/chat/editor.py +71 -0
- cli/tui/chat/messages.py +223 -0
- cli/tui/chat/pane.py +141 -0
- cli/tui/chat/renderers/__init__.py +0 -0
- cli/tui/chat/renderers/base.py +72 -0
- cli/tui/chat/renderers/markdown.py +250 -0
- cli/tui/chat/renderers/plain.py +83 -0
- cli/tui/chat/screen.py +1155 -0
- cli/tui/chat/services/__init__.py +0 -0
- cli/tui/chat/services/injection.py +386 -0
- cli/tui/chat/services/name_generator.py +256 -0
- cli/tui/chat/slash_commands.py +424 -0
- cli/tui/chat/store.py +280 -0
- cli/tui/chat/types.py +220 -0
- cli/tui/chat/widgets/__init__.py +0 -0
- cli/tui/chat/widgets/chat_header.py +75 -0
- cli/tui/chat/widgets/chat_input.py +362 -0
- cli/tui/chat/widgets/injection_popup.py +161 -0
- cli/tui/chat/widgets/message_display.py +287 -0
- cli/tui/chat/widgets/session_sidebar.py +214 -0
- cli/tui/chat/widgets/welcome_screen.py +290 -0
- cli/tui/screens/__init__.py +0 -0
- cli/tui/screens/agents.py +344 -0
- cli/tui/screens/base.py +301 -0
- cli/tui/screens/content.py +508 -0
- cli/tui/screens/dashboard.py +89 -0
- cli/tui/screens/models.py +96 -0
- cli/tui/screens/nav_screen.py +186 -0
- cli/tui/screens/schemas.py +522 -0
- cli/tui/screens/templates.py +734 -0
- cli/tui/screens/tool_configs.py +335 -0
- cli/tui/styles/__init__.py +0 -0
- cli/tui/widgets/__init__.py +0 -0
- cli/tui/widgets/agent_create_modal.py +139 -0
- cli/tui/widgets/agent_form_modal.py +659 -0
- cli/tui/widgets/agent_update_modal.py +299 -0
- cli/tui/widgets/base_form_modal.py +77 -0
- cli/tui/widgets/confirm_modal.py +75 -0
- cli/tui/widgets/help_modal.py +145 -0
- cli/tui/widgets/new_session_modal.py +328 -0
- cli/tui/widgets/schema_create_modal.py +271 -0
- cli/tui/widgets/schema_update_modal.py +188 -0
- cli/tui/widgets/status_footer.py +147 -0
- cli/tui/widgets/template_create_modal.py +502 -0
- cli/tui/widgets/template_update_modal.py +308 -0
- cli/tui/widgets/tool_config_create_modal.py +216 -0
- cli/tui/widgets/tool_config_update_modal.py +208 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""JSON to JSON Schema builder component.
|
|
2
|
+
|
|
3
|
+
Allows users to paste a JSON example and automatically generates
|
|
4
|
+
a JSON schema using Genson. Shows real-time validation errors.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from typing import Any, cast
|
|
9
|
+
|
|
10
|
+
from genson import SchemaBuilder
|
|
11
|
+
from textual import on
|
|
12
|
+
from textual.app import ComposeResult
|
|
13
|
+
from textual.containers import Vertical
|
|
14
|
+
from textual.message import Message
|
|
15
|
+
from textual.widget import Widget
|
|
16
|
+
from textual.widgets import Static, TextArea
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def generate_schema_from_json(json_str: str) -> dict[str, Any] | None:
|
|
20
|
+
"""Generate JSON schema from a JSON example string.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
json_str: A JSON string representing an example object
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
The generated JSON schema as a dict, or None if invalid JSON
|
|
27
|
+
"""
|
|
28
|
+
if not json_str.strip():
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
data = json.loads(json_str)
|
|
32
|
+
builder: Any = SchemaBuilder()
|
|
33
|
+
builder.add_object(data)
|
|
34
|
+
return cast(dict[str, Any], builder.to_schema())
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class JsonSchemaBuilder(Widget):
|
|
38
|
+
"""Widget that converts JSON examples to JSON Schema.
|
|
39
|
+
|
|
40
|
+
Users paste a JSON object example, and this widget automatically
|
|
41
|
+
generates the corresponding JSON schema using Genson.
|
|
42
|
+
|
|
43
|
+
Usage:
|
|
44
|
+
yield JsonSchemaBuilder(id="output-schema-builder")
|
|
45
|
+
|
|
46
|
+
# Get the generated schema
|
|
47
|
+
builder = self.query_one("#output-schema-builder", JsonSchemaBuilder)
|
|
48
|
+
schema = builder.generated_schema # dict or None
|
|
49
|
+
|
|
50
|
+
Features:
|
|
51
|
+
- Real-time JSON validation with error display
|
|
52
|
+
- Auto-generates schema as user types
|
|
53
|
+
- Shows preview of generated schema
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
class SchemaChanged(Message):
|
|
57
|
+
"""Posted when the generated schema changes."""
|
|
58
|
+
|
|
59
|
+
def __init__(self, schema: dict[str, Any] | None) -> None:
|
|
60
|
+
self.schema = schema
|
|
61
|
+
super().__init__()
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
placeholder: str = '{"name": "John", "age": 30, "active": true}',
|
|
66
|
+
**kwargs: Any,
|
|
67
|
+
) -> None:
|
|
68
|
+
super().__init__(**kwargs)
|
|
69
|
+
self._placeholder = placeholder
|
|
70
|
+
self._generated_schema: dict[str, Any] | None = None
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def generated_schema(self) -> dict[str, Any] | None:
|
|
74
|
+
"""Get the generated JSON schema, or None if no valid input."""
|
|
75
|
+
return self._generated_schema
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def json_input(self) -> str:
|
|
79
|
+
"""Get the raw JSON input text."""
|
|
80
|
+
return self.query_one("#json-input", TextArea).text.strip()
|
|
81
|
+
|
|
82
|
+
def compose(self) -> ComposeResult:
|
|
83
|
+
with Vertical():
|
|
84
|
+
yield TextArea(
|
|
85
|
+
placeholder=self._placeholder,
|
|
86
|
+
id="json-input",
|
|
87
|
+
classes="json-input",
|
|
88
|
+
)
|
|
89
|
+
yield Static("", classes="error-label hidden", id="json-error")
|
|
90
|
+
yield Static("Generated Schema:", classes="section-label")
|
|
91
|
+
yield TextArea(
|
|
92
|
+
"Paste a JSON example above to generate schema",
|
|
93
|
+
read_only=True,
|
|
94
|
+
id="schema-preview",
|
|
95
|
+
classes="schema-preview",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
@on(TextArea.Changed, "#json-input")
|
|
99
|
+
def on_json_changed(self, event: TextArea.Changed) -> None:
|
|
100
|
+
"""Validate JSON and generate schema as user types."""
|
|
101
|
+
self._update_schema(event.text_area.text)
|
|
102
|
+
|
|
103
|
+
def _update_schema(self, json_text: str) -> None:
|
|
104
|
+
"""Update the generated schema from JSON input."""
|
|
105
|
+
error_label = self.query_one("#json-error", Static)
|
|
106
|
+
preview = self.query_one("#schema-preview", TextArea)
|
|
107
|
+
|
|
108
|
+
json_text = json_text.strip()
|
|
109
|
+
|
|
110
|
+
if not json_text:
|
|
111
|
+
# Empty input
|
|
112
|
+
self._generated_schema = None
|
|
113
|
+
error_label.update("")
|
|
114
|
+
error_label.add_class("hidden")
|
|
115
|
+
preview.text = "Paste a JSON example above to generate schema"
|
|
116
|
+
self.post_message(self.SchemaChanged(None))
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
schema = generate_schema_from_json(json_text)
|
|
121
|
+
self._generated_schema = schema
|
|
122
|
+
|
|
123
|
+
# Show success - display the schema
|
|
124
|
+
error_label.update("")
|
|
125
|
+
error_label.add_class("hidden")
|
|
126
|
+
|
|
127
|
+
if schema:
|
|
128
|
+
schema_str = json.dumps(schema, indent=2)
|
|
129
|
+
preview.text = schema_str
|
|
130
|
+
else:
|
|
131
|
+
preview.text = "Empty schema generated"
|
|
132
|
+
|
|
133
|
+
self.post_message(self.SchemaChanged(schema))
|
|
134
|
+
|
|
135
|
+
except json.JSONDecodeError as e:
|
|
136
|
+
# Show error
|
|
137
|
+
self._generated_schema = None
|
|
138
|
+
error_label.update(
|
|
139
|
+
f"Invalid JSON: {e.msg} (line {e.lineno}, col {e.colno})"
|
|
140
|
+
)
|
|
141
|
+
error_label.remove_class("hidden")
|
|
142
|
+
preview.text = "Fix JSON errors to generate schema"
|
|
143
|
+
self.post_message(self.SchemaChanged(None))
|
|
144
|
+
|
|
145
|
+
def clear(self) -> None:
|
|
146
|
+
"""Clear the input and generated schema."""
|
|
147
|
+
self.query_one("#json-input", TextArea).clear()
|
|
148
|
+
self._generated_schema = None
|
|
149
|
+
self._update_schema("")
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Model picker widget with search and multi-select.
|
|
2
|
+
|
|
3
|
+
A search input that uses the ApiClient to fetch models on each keystroke,
|
|
4
|
+
displaying results in a static list below. Users can select multiple
|
|
5
|
+
models (primary + fallbacks) using Space or Enter.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from textual import on
|
|
11
|
+
from textual.message import Message
|
|
12
|
+
from textual.widgets import OptionList
|
|
13
|
+
|
|
14
|
+
from alloy_runtime_sdk.api_client.client import ApiClient
|
|
15
|
+
from alloy_runtime_types.dtos.models import ModelListItemResponse
|
|
16
|
+
|
|
17
|
+
from cli.infrastructure.forms.base_picker import MultiSelectPicker
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ModelPicker(MultiSelectPicker[ModelListItemResponse]):
|
|
21
|
+
"""Multi-select model picker with live search.
|
|
22
|
+
|
|
23
|
+
Uses the ApiClient to fetch models on each keystroke. Results appear in a
|
|
24
|
+
static box below the search input.
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
yield ModelPicker(
|
|
28
|
+
client=server_client,
|
|
29
|
+
id="models"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Get selected models
|
|
33
|
+
picker = self.query_one("#models", ModelPicker)
|
|
34
|
+
models = picker.selected_models # List[ModelListItemResponse]
|
|
35
|
+
|
|
36
|
+
Keybindings:
|
|
37
|
+
- Type to search (fetches from API)
|
|
38
|
+
- Up/Down to navigate results
|
|
39
|
+
- Space or Enter to select/deselect
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
class ModelsChanged(Message):
|
|
43
|
+
"""Posted when selected models change."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, models: list[ModelListItemResponse]) -> None:
|
|
46
|
+
self.models = models
|
|
47
|
+
super().__init__()
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
client: ApiClient,
|
|
52
|
+
placeholder: str = "Type to search models (e.g., gpt-4, claude, sonnet)...",
|
|
53
|
+
**kwargs: Any,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Initialize the model picker.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
client: ApiClient instance for API calls
|
|
59
|
+
placeholder: Placeholder text for search input
|
|
60
|
+
"""
|
|
61
|
+
super().__init__(
|
|
62
|
+
client=client,
|
|
63
|
+
placeholder=placeholder,
|
|
64
|
+
empty_message="No models selected",
|
|
65
|
+
**kwargs,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def selected_models(self) -> list[ModelListItemResponse]:
|
|
70
|
+
"""Get the list of selected models in order (primary first, then fallbacks)."""
|
|
71
|
+
return self.selected_items
|
|
72
|
+
|
|
73
|
+
async def _fetch_items(self, query: str) -> list[ModelListItemResponse]:
|
|
74
|
+
"""Fetch models matching query from the API."""
|
|
75
|
+
response = await self._client.list_models(
|
|
76
|
+
search=query,
|
|
77
|
+
configured_only=True,
|
|
78
|
+
limit=100,
|
|
79
|
+
)
|
|
80
|
+
return list(response.models)
|
|
81
|
+
|
|
82
|
+
def _format_item(self, item: ModelListItemResponse) -> str:
|
|
83
|
+
"""Format model for display in option list."""
|
|
84
|
+
return f"{item.provider_key}:{item.provider_model_name}"
|
|
85
|
+
|
|
86
|
+
def _get_item_id(self, item: ModelListItemResponse) -> str:
|
|
87
|
+
"""Get unique identifier for a model."""
|
|
88
|
+
return f"{item.provider_key}:{item.provider_model_name}"
|
|
89
|
+
|
|
90
|
+
def _get_result_count_message(self, count: int) -> str:
|
|
91
|
+
"""Get status message for result count."""
|
|
92
|
+
if count == 0:
|
|
93
|
+
return "No models found"
|
|
94
|
+
return f"{count} model{'s' if count != 1 else ''} found - Space/Enter to select"
|
|
95
|
+
|
|
96
|
+
def _format_selection_display(self) -> str | None:
|
|
97
|
+
"""Format selected models as an ordered list."""
|
|
98
|
+
if not self._selected_items:
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
lines: list[str] = []
|
|
102
|
+
for i, model in enumerate(self._selected_items.values()):
|
|
103
|
+
model_label = f"{model.provider_key}:{model.provider_model_name}"
|
|
104
|
+
if i == 0:
|
|
105
|
+
lines.append(f"[bold green]1. {model_label}[/] (primary)")
|
|
106
|
+
else:
|
|
107
|
+
lines.append(f"{i + 1}. {model_label}")
|
|
108
|
+
return "\n".join(lines)
|
|
109
|
+
|
|
110
|
+
def _post_selection_changed(self) -> None:
|
|
111
|
+
"""Post ModelsChanged message instead of generic SelectionChanged."""
|
|
112
|
+
self.post_message(self.ModelsChanged(self.selected_models))
|
|
113
|
+
|
|
114
|
+
def on_key(self, event: Any) -> None:
|
|
115
|
+
"""Handle space key to toggle selection."""
|
|
116
|
+
if event.key == "space":
|
|
117
|
+
options = self.query_one("#search-options", OptionList)
|
|
118
|
+
if options.highlighted is not None:
|
|
119
|
+
self._handle_item_selection(options.highlighted)
|
|
120
|
+
event.prevent_default()
|
|
121
|
+
event.stop()
|
|
122
|
+
|
|
123
|
+
@on(OptionList.OptionHighlighted, "#search-options")
|
|
124
|
+
def on_option_highlighted(self, event: OptionList.OptionHighlighted) -> None:
|
|
125
|
+
"""Handle highlighting - space key handling is done via on_key."""
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
def set_initial_models(self, models: list[ModelListItemResponse]) -> None:
|
|
129
|
+
"""Pre-populate the picker with existing model selections.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
models: List of ModelListItemResponse objects to pre-select
|
|
133
|
+
"""
|
|
134
|
+
self.set_initial_selections(models)
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Form value parsing utilities.
|
|
2
|
+
|
|
3
|
+
Provides reusable parsing methods for common form input types with
|
|
4
|
+
validation and user-friendly error messages.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from cli.infrastructure.forms.parsers import FormParsers
|
|
8
|
+
|
|
9
|
+
class MyWidget(Widget, FormParsers):
|
|
10
|
+
def _parse_values(self):
|
|
11
|
+
temperature = self.parse_float(
|
|
12
|
+
self.query_one("#temperature", Input).value,
|
|
13
|
+
"Temperature",
|
|
14
|
+
min_val=0.0,
|
|
15
|
+
max_val=2.0
|
|
16
|
+
)
|
|
17
|
+
user_id = self.parse_uuid(self.query_one("#user_id", Input).value, "User ID")
|
|
18
|
+
config = self.parse_json(self.query_one("#config", TextArea).text, "Config")
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
from typing import Any, cast
|
|
23
|
+
from uuid import UUID
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class FormParsers:
|
|
27
|
+
"""Mixin providing common value parsing methods for form apps.
|
|
28
|
+
|
|
29
|
+
All parsing methods follow the same pattern:
|
|
30
|
+
- Return None for empty strings
|
|
31
|
+
- Raise ValueError with user-friendly message on invalid input
|
|
32
|
+
- Support optional bounds/constraints
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def parse_uuid(self, value: str, field_name: str) -> UUID | None:
|
|
36
|
+
"""Parse UUID string or return None for empty input.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
value: String value to parse (may be empty)
|
|
40
|
+
field_name: Human-readable field name for error messages
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Parsed UUID or None if input is empty
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
ValueError: If value is non-empty but not a valid UUID
|
|
47
|
+
"""
|
|
48
|
+
if not value:
|
|
49
|
+
return None
|
|
50
|
+
try:
|
|
51
|
+
return UUID(value)
|
|
52
|
+
except ValueError:
|
|
53
|
+
raise ValueError(f"{field_name} must be a valid UUID")
|
|
54
|
+
|
|
55
|
+
def parse_float(
|
|
56
|
+
self,
|
|
57
|
+
value: str,
|
|
58
|
+
field_name: str,
|
|
59
|
+
min_val: float | None = None,
|
|
60
|
+
max_val: float | None = None,
|
|
61
|
+
) -> float | None:
|
|
62
|
+
"""Parse float with optional bounds validation.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
value: String value to parse (may be empty)
|
|
66
|
+
field_name: Human-readable field name for error messages
|
|
67
|
+
min_val: Optional minimum value (inclusive)
|
|
68
|
+
max_val: Optional maximum value (inclusive)
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Parsed float or None if input is empty
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
ValueError: If value is non-empty but not a valid float,
|
|
75
|
+
or if value is outside specified bounds
|
|
76
|
+
"""
|
|
77
|
+
if not value:
|
|
78
|
+
return None
|
|
79
|
+
try:
|
|
80
|
+
f = float(value)
|
|
81
|
+
if min_val is not None and f < min_val:
|
|
82
|
+
raise ValueError(f"{field_name} must be >= {min_val}")
|
|
83
|
+
if max_val is not None and f > max_val:
|
|
84
|
+
raise ValueError(f"{field_name} must be <= {max_val}")
|
|
85
|
+
return f
|
|
86
|
+
except ValueError as e:
|
|
87
|
+
if "must be" in str(e):
|
|
88
|
+
raise
|
|
89
|
+
raise ValueError(f"{field_name} must be a number")
|
|
90
|
+
|
|
91
|
+
def parse_int(
|
|
92
|
+
self,
|
|
93
|
+
value: str,
|
|
94
|
+
field_name: str,
|
|
95
|
+
min_val: int | None = None,
|
|
96
|
+
max_val: int | None = None,
|
|
97
|
+
) -> int | None:
|
|
98
|
+
"""Parse integer with optional bounds validation.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
value: String value to parse (may be empty)
|
|
102
|
+
field_name: Human-readable field name for error messages
|
|
103
|
+
min_val: Optional minimum value (inclusive)
|
|
104
|
+
max_val: Optional maximum value (inclusive)
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Parsed integer or None if input is empty
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
ValueError: If value is non-empty but not a valid integer,
|
|
111
|
+
or if value is outside specified bounds
|
|
112
|
+
"""
|
|
113
|
+
if not value:
|
|
114
|
+
return None
|
|
115
|
+
try:
|
|
116
|
+
i = int(value)
|
|
117
|
+
if min_val is not None and i < min_val:
|
|
118
|
+
raise ValueError(f"{field_name} must be >= {min_val}")
|
|
119
|
+
if max_val is not None and i > max_val:
|
|
120
|
+
raise ValueError(f"{field_name} must be <= {max_val}")
|
|
121
|
+
return i
|
|
122
|
+
except ValueError as e:
|
|
123
|
+
if "must be" in str(e):
|
|
124
|
+
raise
|
|
125
|
+
raise ValueError(f"{field_name} must be an integer")
|
|
126
|
+
|
|
127
|
+
def parse_json(self, value: str, field_name: str) -> dict[str, Any] | None:
|
|
128
|
+
"""Parse JSON string to dict.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
value: JSON string to parse (may be empty)
|
|
132
|
+
field_name: Human-readable field name for error messages
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Parsed dict or None if input is empty
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
ValueError: If value is non-empty but not valid JSON,
|
|
139
|
+
or if JSON is not an object (dict)
|
|
140
|
+
"""
|
|
141
|
+
if not value:
|
|
142
|
+
return None
|
|
143
|
+
try:
|
|
144
|
+
result = json.loads(value)
|
|
145
|
+
if not isinstance(result, dict):
|
|
146
|
+
raise ValueError(f"{field_name} must be a JSON object")
|
|
147
|
+
return cast(dict[str, Any], result)
|
|
148
|
+
except json.JSONDecodeError as e:
|
|
149
|
+
raise ValueError(f"{field_name} has invalid JSON: {e}")
|
|
150
|
+
|
|
151
|
+
def parse_json_list(self, value: str, field_name: str) -> list[Any] | None:
|
|
152
|
+
"""Parse JSON string to list.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
value: JSON string to parse (may be empty)
|
|
156
|
+
field_name: Human-readable field name for error messages
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Parsed list or None if input is empty
|
|
160
|
+
|
|
161
|
+
Raises:
|
|
162
|
+
ValueError: If value is non-empty but not valid JSON,
|
|
163
|
+
or if JSON is not an array (list)
|
|
164
|
+
"""
|
|
165
|
+
if not value:
|
|
166
|
+
return None
|
|
167
|
+
try:
|
|
168
|
+
result = json.loads(value)
|
|
169
|
+
if not isinstance(result, list):
|
|
170
|
+
raise ValueError(f"{field_name} must be a JSON array")
|
|
171
|
+
return cast(list[Any], result)
|
|
172
|
+
except json.JSONDecodeError as e:
|
|
173
|
+
raise ValueError(f"{field_name} has invalid JSON: {e}")
|