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,302 @@
|
|
|
1
|
+
"""Parser for macro injection syntax in chat messages.
|
|
2
|
+
|
|
3
|
+
Parses injection patterns from user messages using the same syntax as templates:
|
|
4
|
+
- @fragment(name) - Include a fragment template
|
|
5
|
+
- @text(name) - Insert text from a content part
|
|
6
|
+
- @json(name) - Insert JSON from a content part
|
|
7
|
+
- @schema(name) - Reference a schema definition
|
|
8
|
+
|
|
9
|
+
Note: Unlike template macros which use Jinja2 rendering, these are resolved
|
|
10
|
+
at message send time by fetching the referenced content from the API.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class FragmentReference:
|
|
19
|
+
"""A reference to a fragment template found in user input."""
|
|
20
|
+
|
|
21
|
+
identifier: str # Fragment name or UUID
|
|
22
|
+
raw_match: str = "" # Original matched text
|
|
23
|
+
start: int = 0 # Start position in message
|
|
24
|
+
end: int = 0 # End position in message
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class ContentReference:
|
|
29
|
+
"""A reference to content found in user input."""
|
|
30
|
+
|
|
31
|
+
identifier: str # Content name/UUID
|
|
32
|
+
is_json: bool = False # True for @json, False for @text
|
|
33
|
+
raw_match: str = "" # Original matched text
|
|
34
|
+
start: int = 0 # Start position in message
|
|
35
|
+
end: int = 0 # End position in message
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class SchemaReference:
|
|
40
|
+
"""A reference to a schema found in user input."""
|
|
41
|
+
|
|
42
|
+
identifier: str # Schema name or UUID
|
|
43
|
+
raw_match: str = "" # Original matched text
|
|
44
|
+
start: int = 0 # Start position in message
|
|
45
|
+
end: int = 0 # End position in message
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Pattern for @fragment(identifier)
|
|
49
|
+
# Supports identifiers with dots, dashes, underscores, alphanumeric, and UUIDs
|
|
50
|
+
# No quotes required (matches template macro syntax)
|
|
51
|
+
FRAGMENT_PATTERN = re.compile(
|
|
52
|
+
r"@fragment\(\s*([a-zA-Z0-9._-]+)\s*\)", # @fragment(identifier)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Pattern for @text(identifier) - text content
|
|
56
|
+
TEXT_PATTERN = re.compile(
|
|
57
|
+
r"@text\(\s*([a-zA-Z0-9._-]+)\s*\)", # @text(identifier)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Pattern for @json(identifier) - JSON content
|
|
61
|
+
JSON_PATTERN = re.compile(
|
|
62
|
+
r"@json\(\s*([a-zA-Z0-9._-]+)\s*\)", # @json(identifier)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Pattern for @schema(identifier) - schema definition
|
|
66
|
+
SCHEMA_PATTERN = re.compile(
|
|
67
|
+
r"@schema\(\s*([a-zA-Z0-9._-]+)\s*\)", # @schema(identifier)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Pattern for detecting partial injection triggers (for autocomplete)
|
|
71
|
+
# Matches: @fragment, @fragment(, @fragment(partial
|
|
72
|
+
PARTIAL_FRAGMENT_PATTERN = re.compile(r"@fragment(?:\(\s*([a-zA-Z0-9._-]*)?)?$")
|
|
73
|
+
PARTIAL_TEXT_PATTERN = re.compile(r"@text(?:\(\s*([a-zA-Z0-9._-]*)?)?$")
|
|
74
|
+
PARTIAL_JSON_PATTERN = re.compile(r"@json(?:\(\s*([a-zA-Z0-9._-]*)?)?$")
|
|
75
|
+
PARTIAL_SCHEMA_PATTERN = re.compile(r"@schema(?:\(\s*([a-zA-Z0-9._-]*)?)?$")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def parse_fragments(message: str) -> list[FragmentReference]:
|
|
79
|
+
"""Parse all @fragment references from a message.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
message: User input message
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
List of FragmentReference objects found in the message
|
|
86
|
+
"""
|
|
87
|
+
references: list[FragmentReference] = []
|
|
88
|
+
|
|
89
|
+
for match in FRAGMENT_PATTERN.finditer(message):
|
|
90
|
+
references.append(
|
|
91
|
+
FragmentReference(
|
|
92
|
+
identifier=match.group(1),
|
|
93
|
+
raw_match=match.group(0),
|
|
94
|
+
start=match.start(),
|
|
95
|
+
end=match.end(),
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return references
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def parse_content(message: str) -> list[ContentReference]:
|
|
103
|
+
"""Parse all @text and @json references from a message.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
message: User input message
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
List of ContentReference objects found in the message
|
|
110
|
+
"""
|
|
111
|
+
references: list[ContentReference] = []
|
|
112
|
+
|
|
113
|
+
# Parse @text(identifier)
|
|
114
|
+
for match in TEXT_PATTERN.finditer(message):
|
|
115
|
+
references.append(
|
|
116
|
+
ContentReference(
|
|
117
|
+
identifier=match.group(1),
|
|
118
|
+
is_json=False,
|
|
119
|
+
raw_match=match.group(0),
|
|
120
|
+
start=match.start(),
|
|
121
|
+
end=match.end(),
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Parse @json(identifier)
|
|
126
|
+
for match in JSON_PATTERN.finditer(message):
|
|
127
|
+
references.append(
|
|
128
|
+
ContentReference(
|
|
129
|
+
identifier=match.group(1),
|
|
130
|
+
is_json=True,
|
|
131
|
+
raw_match=match.group(0),
|
|
132
|
+
start=match.start(),
|
|
133
|
+
end=match.end(),
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return references
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def parse_schemas(message: str) -> list[SchemaReference]:
|
|
141
|
+
"""Parse all @schema references from a message.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
message: User input message
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
List of SchemaReference objects found in the message
|
|
148
|
+
"""
|
|
149
|
+
references: list[SchemaReference] = []
|
|
150
|
+
|
|
151
|
+
for match in SCHEMA_PATTERN.finditer(message):
|
|
152
|
+
references.append(
|
|
153
|
+
SchemaReference(
|
|
154
|
+
identifier=match.group(1),
|
|
155
|
+
raw_match=match.group(0),
|
|
156
|
+
start=match.start(),
|
|
157
|
+
end=match.end(),
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
return references
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def parse_injections(
|
|
165
|
+
message: str,
|
|
166
|
+
) -> tuple[list[FragmentReference], list[ContentReference], list[SchemaReference]]:
|
|
167
|
+
"""Parse all injection patterns from a message.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
message: User input message
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Tuple of (fragment_references, content_references, schema_references)
|
|
174
|
+
"""
|
|
175
|
+
return parse_fragments(message), parse_content(message), parse_schemas(message)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def has_injections(message: str) -> bool:
|
|
179
|
+
"""Check if a message contains any injection patterns.
|
|
180
|
+
|
|
181
|
+
This is a quick check that doesn't do full parsing.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
message: User input message
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
True if message contains @fragment, @text, @json, or @schema patterns
|
|
188
|
+
"""
|
|
189
|
+
return (
|
|
190
|
+
"@fragment(" in message
|
|
191
|
+
or "@text(" in message
|
|
192
|
+
or "@json(" in message
|
|
193
|
+
or "@schema(" in message
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dataclass
|
|
198
|
+
class PartialInjection:
|
|
199
|
+
"""A partial injection pattern detected for autocomplete."""
|
|
200
|
+
|
|
201
|
+
injection_type: str # "fragment", "text", "json", or "schema"
|
|
202
|
+
partial_identifier: str # Partially typed identifier (may be empty)
|
|
203
|
+
start: int # Start position of the @ in the message
|
|
204
|
+
end: int # Current end position (cursor)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def detect_partial_injection(
|
|
208
|
+
message: str, cursor_pos: int | None = None
|
|
209
|
+
) -> PartialInjection | None:
|
|
210
|
+
"""Detect if the cursor is in a partial injection pattern.
|
|
211
|
+
|
|
212
|
+
Used for autocomplete - detects when user is typing an injection.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
message: User input message
|
|
216
|
+
cursor_pos: Cursor position (defaults to end of message)
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
PartialInjection if cursor is in a partial pattern, None otherwise
|
|
220
|
+
|
|
221
|
+
Examples:
|
|
222
|
+
>>> detect_partial_injection("Hello @fragment")
|
|
223
|
+
PartialInjection(injection_type='fragment', partial_identifier='', ...)
|
|
224
|
+
>>> detect_partial_injection("Hello @fragment(my-te")
|
|
225
|
+
PartialInjection(injection_type='fragment', partial_identifier='my-te', ...)
|
|
226
|
+
"""
|
|
227
|
+
if cursor_pos is None:
|
|
228
|
+
cursor_pos = len(message)
|
|
229
|
+
|
|
230
|
+
# Get text up to cursor
|
|
231
|
+
text_to_cursor = message[:cursor_pos]
|
|
232
|
+
|
|
233
|
+
# Find the last @ before cursor
|
|
234
|
+
last_at = text_to_cursor.rfind("@")
|
|
235
|
+
if last_at == -1:
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
# Get the text from @ to cursor
|
|
239
|
+
partial_text = text_to_cursor[last_at:]
|
|
240
|
+
|
|
241
|
+
# Try to match each partial pattern
|
|
242
|
+
patterns = [
|
|
243
|
+
(PARTIAL_FRAGMENT_PATTERN, "fragment"),
|
|
244
|
+
(PARTIAL_TEXT_PATTERN, "text"),
|
|
245
|
+
(PARTIAL_JSON_PATTERN, "json"),
|
|
246
|
+
(PARTIAL_SCHEMA_PATTERN, "schema"),
|
|
247
|
+
]
|
|
248
|
+
|
|
249
|
+
for pattern, injection_type in patterns:
|
|
250
|
+
match = pattern.match(partial_text)
|
|
251
|
+
if match:
|
|
252
|
+
# Extract partial identifier if present
|
|
253
|
+
partial_identifier = (
|
|
254
|
+
match.group(1) if match.lastindex and match.group(1) else ""
|
|
255
|
+
)
|
|
256
|
+
return PartialInjection(
|
|
257
|
+
injection_type=injection_type,
|
|
258
|
+
partial_identifier=partial_identifier,
|
|
259
|
+
start=last_at,
|
|
260
|
+
end=cursor_pos,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def format_fragment_injection(identifier: str) -> str:
|
|
267
|
+
"""Format a fragment injection string.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
identifier: Fragment name or UUID
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Formatted injection string like @fragment(name)
|
|
274
|
+
"""
|
|
275
|
+
return f"@fragment({identifier})"
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def format_content_injection(identifier: str, is_json: bool = False) -> str:
|
|
279
|
+
"""Format a content injection string.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
identifier: Content identifier (name or UUID)
|
|
283
|
+
is_json: Whether to use @json
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
Formatted injection string
|
|
287
|
+
"""
|
|
288
|
+
if is_json:
|
|
289
|
+
return f"@json({identifier})"
|
|
290
|
+
return f"@text({identifier})"
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def format_schema_injection(identifier: str) -> str:
|
|
294
|
+
"""Format a schema injection string.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
identifier: Schema name or UUID
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Formatted injection string
|
|
301
|
+
"""
|
|
302
|
+
return f"@schema({identifier})"
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
"""Resolver for macro injection patterns in chat messages.
|
|
2
|
+
|
|
3
|
+
This module handles:
|
|
4
|
+
1. Resolving @fragment references by calling the template render API
|
|
5
|
+
2. Resolving @text references by fetching text content from the content API
|
|
6
|
+
3. Resolving @json references by fetching structured content from the content API
|
|
7
|
+
4. Resolving @schema references by fetching schema definitions from the schema API
|
|
8
|
+
5. Replacing patterns in the original message with resolved content
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import re
|
|
13
|
+
from collections.abc import Awaitable, Callable
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from types import TracebackType
|
|
16
|
+
from typing import Any, cast
|
|
17
|
+
|
|
18
|
+
from cli.infrastructure.injection.parser import (
|
|
19
|
+
ContentReference,
|
|
20
|
+
FragmentReference,
|
|
21
|
+
SchemaReference,
|
|
22
|
+
parse_injections,
|
|
23
|
+
)
|
|
24
|
+
from alloy_runtime_sdk.api_client.client import ApiClient
|
|
25
|
+
from alloy_runtime_types.dtos.templates import (
|
|
26
|
+
RenderTemplateRequest,
|
|
27
|
+
)
|
|
28
|
+
from alloy_runtime_sdk.exceptions.errors import (
|
|
29
|
+
AuthenticationError,
|
|
30
|
+
ForbiddenError,
|
|
31
|
+
NotFoundError,
|
|
32
|
+
ServerError,
|
|
33
|
+
ValidationError,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _empty_details() -> dict[str, Any]:
|
|
38
|
+
return {}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _empty_str_list() -> list[str]:
|
|
42
|
+
return []
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _empty_error_list() -> list["InjectionError"]:
|
|
46
|
+
return []
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class InjectionError:
|
|
51
|
+
"""An error that occurred during injection resolution."""
|
|
52
|
+
|
|
53
|
+
pattern: str # The pattern that failed (e.g., @fragment(name))
|
|
54
|
+
error_type: str # Type of error (e.g., "not_found", "permission_denied")
|
|
55
|
+
message: str # Human-readable error message
|
|
56
|
+
details: dict[str, Any] = field(default_factory=_empty_details)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class ResolvedMessage:
|
|
61
|
+
"""Result of resolving all injection patterns in a message."""
|
|
62
|
+
|
|
63
|
+
text: str # Final message with all patterns resolved
|
|
64
|
+
fragments_used: list[str] = field(default_factory=_empty_str_list)
|
|
65
|
+
content_used: list[str] = field(default_factory=_empty_str_list)
|
|
66
|
+
schemas_used: list[str] = field(default_factory=_empty_str_list)
|
|
67
|
+
errors: list[InjectionError] = field(default_factory=_empty_error_list)
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def has_errors(self) -> bool:
|
|
71
|
+
"""Check if any errors occurred during resolution."""
|
|
72
|
+
return len(self.errors) > 0
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def is_resolved(self) -> bool:
|
|
76
|
+
"""Check if message was successfully resolved without errors."""
|
|
77
|
+
return not self.has_errors
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class InjectionResolver:
|
|
81
|
+
"""Resolves macro injection patterns via API calls.
|
|
82
|
+
|
|
83
|
+
Supports the same macro syntax as templates:
|
|
84
|
+
- @fragment(name) - Include a fragment template
|
|
85
|
+
- @text(name) - Insert text from a content part
|
|
86
|
+
- @json(name) - Insert JSON from a content part
|
|
87
|
+
- @schema(name) - Reference a schema definition
|
|
88
|
+
|
|
89
|
+
This resolver can work with either:
|
|
90
|
+
1. Direct initialization with api_url/api_key (creates its own client)
|
|
91
|
+
2. Initialization with an existing ApiClient instance
|
|
92
|
+
|
|
93
|
+
The TUI should use the client-based initialization to share the client.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(
|
|
97
|
+
self,
|
|
98
|
+
api_url: str | None = None,
|
|
99
|
+
api_key: str | None = None,
|
|
100
|
+
client: ApiClient | None = None,
|
|
101
|
+
):
|
|
102
|
+
"""Initialize the resolver.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
api_url: Base URL for the API (used if client not provided)
|
|
106
|
+
api_key: API key for authentication (used if client not provided)
|
|
107
|
+
client: Existing ApiClient instance (preferred for TUI)
|
|
108
|
+
"""
|
|
109
|
+
if client is not None:
|
|
110
|
+
self._client = client
|
|
111
|
+
self._owns_client = False
|
|
112
|
+
elif api_url and api_key:
|
|
113
|
+
self.api_url = api_url.rstrip("/")
|
|
114
|
+
self.api_key = api_key
|
|
115
|
+
self._client = None
|
|
116
|
+
self._owns_client = True
|
|
117
|
+
else:
|
|
118
|
+
raise ValueError("Must provide either client or both api_url and api_key")
|
|
119
|
+
|
|
120
|
+
async def _get_client(self) -> ApiClient:
|
|
121
|
+
"""Get or create server client."""
|
|
122
|
+
if self._client is None:
|
|
123
|
+
self._client = ApiClient(
|
|
124
|
+
base_url=self.api_url, api_key=self.api_key, timeout=30.0
|
|
125
|
+
)
|
|
126
|
+
# Enter the async context manager
|
|
127
|
+
await self._client.__aenter__()
|
|
128
|
+
return self._client
|
|
129
|
+
|
|
130
|
+
async def close(self) -> None:
|
|
131
|
+
"""Close the server client if we own it."""
|
|
132
|
+
if self._owns_client and self._client:
|
|
133
|
+
aexit = cast(
|
|
134
|
+
Callable[
|
|
135
|
+
[
|
|
136
|
+
type[BaseException] | None,
|
|
137
|
+
BaseException | None,
|
|
138
|
+
TracebackType | None,
|
|
139
|
+
],
|
|
140
|
+
Awaitable[None],
|
|
141
|
+
],
|
|
142
|
+
self._client.__aexit__,
|
|
143
|
+
)
|
|
144
|
+
await aexit(None, None, None)
|
|
145
|
+
self._client = None
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
def _is_valid_uuid(value: str) -> bool:
|
|
149
|
+
"""Check if a string is a valid UUID format."""
|
|
150
|
+
uuid_pattern = re.compile(
|
|
151
|
+
r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
|
|
152
|
+
re.IGNORECASE,
|
|
153
|
+
)
|
|
154
|
+
return bool(uuid_pattern.match(value))
|
|
155
|
+
|
|
156
|
+
async def resolve(self, message: str) -> ResolvedMessage:
|
|
157
|
+
"""Resolve all injection patterns in a message.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
message: User message with injection patterns
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
ResolvedMessage with resolved text and metadata
|
|
164
|
+
"""
|
|
165
|
+
# Parse all patterns
|
|
166
|
+
fragment_refs, content_refs, schema_refs = parse_injections(message)
|
|
167
|
+
|
|
168
|
+
if not fragment_refs and not content_refs and not schema_refs:
|
|
169
|
+
# No injections found - return original message
|
|
170
|
+
return ResolvedMessage(text=message)
|
|
171
|
+
|
|
172
|
+
# Track what we've injected
|
|
173
|
+
fragments_used: list[str] = []
|
|
174
|
+
content_used: list[str] = []
|
|
175
|
+
schemas_used: list[str] = []
|
|
176
|
+
errors: list[InjectionError] = []
|
|
177
|
+
|
|
178
|
+
# Build replacement map: (start, end) -> replacement_text
|
|
179
|
+
# We'll sort by position and replace from end to start to maintain positions
|
|
180
|
+
replacements: list[tuple[int, int, str]] = []
|
|
181
|
+
|
|
182
|
+
# Resolve fragments
|
|
183
|
+
for ref in fragment_refs:
|
|
184
|
+
try:
|
|
185
|
+
rendered_text = await self._resolve_fragment(ref)
|
|
186
|
+
replacements.append((ref.start, ref.end, rendered_text))
|
|
187
|
+
fragments_used.append(ref.identifier)
|
|
188
|
+
except Exception as e:
|
|
189
|
+
error = self._create_error_from_exception(ref.raw_match, e)
|
|
190
|
+
errors.append(error)
|
|
191
|
+
# Keep the original pattern in the message if it fails
|
|
192
|
+
replacements.append((ref.start, ref.end, ref.raw_match))
|
|
193
|
+
|
|
194
|
+
# Resolve content
|
|
195
|
+
for ref in content_refs:
|
|
196
|
+
try:
|
|
197
|
+
content_text = await self._resolve_content(ref)
|
|
198
|
+
replacements.append((ref.start, ref.end, content_text))
|
|
199
|
+
content_used.append(ref.identifier)
|
|
200
|
+
except Exception as e:
|
|
201
|
+
error = self._create_error_from_exception(ref.raw_match, e)
|
|
202
|
+
errors.append(error)
|
|
203
|
+
# Keep the original pattern in the message if it fails
|
|
204
|
+
replacements.append((ref.start, ref.end, ref.raw_match))
|
|
205
|
+
|
|
206
|
+
# Resolve schemas
|
|
207
|
+
for ref in schema_refs:
|
|
208
|
+
try:
|
|
209
|
+
schema_definition = await self._resolve_schema(ref)
|
|
210
|
+
replacements.append((ref.start, ref.end, schema_definition))
|
|
211
|
+
schemas_used.append(ref.identifier)
|
|
212
|
+
except Exception as e:
|
|
213
|
+
error = self._create_error_from_exception(ref.raw_match, e)
|
|
214
|
+
errors.append(error)
|
|
215
|
+
# Keep the original pattern in the message if it fails
|
|
216
|
+
replacements.append((ref.start, ref.end, ref.raw_match))
|
|
217
|
+
|
|
218
|
+
# Sort replacements by start position (descending) so we replace from end to start
|
|
219
|
+
replacements.sort(key=lambda x: x[0], reverse=True)
|
|
220
|
+
|
|
221
|
+
# Apply replacements
|
|
222
|
+
result_text = message
|
|
223
|
+
for start, end, replacement in replacements:
|
|
224
|
+
result_text = result_text[:start] + replacement + result_text[end:]
|
|
225
|
+
|
|
226
|
+
return ResolvedMessage(
|
|
227
|
+
text=result_text,
|
|
228
|
+
fragments_used=fragments_used,
|
|
229
|
+
content_used=content_used,
|
|
230
|
+
schemas_used=schemas_used,
|
|
231
|
+
errors=errors,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
async def _resolve_fragment(self, ref: FragmentReference) -> str:
|
|
235
|
+
"""Resolve a fragment reference by calling the template render API.
|
|
236
|
+
|
|
237
|
+
Fragments are templates with content_type='fragment'. They are rendered
|
|
238
|
+
without variables (fragments don't take variables in the macro syntax).
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
ref: Fragment reference to resolve
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
Rendered fragment text
|
|
245
|
+
"""
|
|
246
|
+
client = await self._get_client()
|
|
247
|
+
|
|
248
|
+
# The render endpoint requires a UUID, so we need to resolve name -> UUID first
|
|
249
|
+
# Check if identifier is already a UUID
|
|
250
|
+
if self._is_valid_uuid(ref.identifier):
|
|
251
|
+
from uuid import UUID
|
|
252
|
+
|
|
253
|
+
template_uuid = UUID(ref.identifier)
|
|
254
|
+
else:
|
|
255
|
+
# Look up template by name to get UUID
|
|
256
|
+
template_uuid = await self._get_template_uuid_by_name(ref.identifier)
|
|
257
|
+
|
|
258
|
+
# Prepare render request (no variables for fragments in macro syntax)
|
|
259
|
+
request_data = RenderTemplateRequest(variables={})
|
|
260
|
+
|
|
261
|
+
# Call render API using server_client
|
|
262
|
+
render_response = await client.render_template(template_uuid, request_data)
|
|
263
|
+
return render_response.rendered_text
|
|
264
|
+
|
|
265
|
+
async def _get_template_uuid_by_name(self, name: str):
|
|
266
|
+
"""Look up a template by name and return its UUID.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
name: Template name (e.g., "my-fragment" or "public.header")
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
Template UUID
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
client = await self._get_client()
|
|
276
|
+
|
|
277
|
+
# GET /templates/{name} returns template details including UUID
|
|
278
|
+
template_response = await client.get_template(name)
|
|
279
|
+
return template_response.template.id
|
|
280
|
+
|
|
281
|
+
async def _resolve_content(self, ref: ContentReference) -> str:
|
|
282
|
+
"""Resolve a content reference by fetching from the content API.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
ref: Content reference to resolve
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
Content text or JSON
|
|
289
|
+
"""
|
|
290
|
+
from uuid import UUID
|
|
291
|
+
|
|
292
|
+
client = await self._get_client()
|
|
293
|
+
|
|
294
|
+
# Content can be referenced by UUID or by searching
|
|
295
|
+
# For now, we only support UUID references
|
|
296
|
+
if not self._is_valid_uuid(ref.identifier):
|
|
297
|
+
raise ValueError(
|
|
298
|
+
f"Invalid content identifier: '{ref.identifier}'. "
|
|
299
|
+
"Content must be referenced by UUID."
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
# Fetch content part using server_client
|
|
303
|
+
content_part = await client.get_content_part(UUID(ref.identifier))
|
|
304
|
+
|
|
305
|
+
# Return appropriate content based on type
|
|
306
|
+
if ref.is_json:
|
|
307
|
+
# User requested JSON format (@json)
|
|
308
|
+
if content_part.content_structured is not None:
|
|
309
|
+
return json.dumps(content_part.content_structured, indent=2)
|
|
310
|
+
elif content_part.content_text is not None:
|
|
311
|
+
# Fall back to text if structured not available
|
|
312
|
+
return content_part.content_text
|
|
313
|
+
else:
|
|
314
|
+
raise ValueError(
|
|
315
|
+
f"Content {ref.identifier} has no text or structured content"
|
|
316
|
+
)
|
|
317
|
+
else:
|
|
318
|
+
# User requested text format (@text)
|
|
319
|
+
if content_part.content_text is not None:
|
|
320
|
+
return content_part.content_text
|
|
321
|
+
elif content_part.content_structured is not None:
|
|
322
|
+
# Fall back to JSON if text not available
|
|
323
|
+
return json.dumps(content_part.content_structured, indent=2)
|
|
324
|
+
else:
|
|
325
|
+
raise ValueError(
|
|
326
|
+
f"Content {ref.identifier} has no text or structured content"
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
async def _resolve_schema(self, ref: SchemaReference) -> str:
|
|
330
|
+
"""Resolve a schema reference by fetching from the schema API.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
ref: Schema reference to resolve
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
Schema definition string
|
|
337
|
+
"""
|
|
338
|
+
client = await self._get_client()
|
|
339
|
+
|
|
340
|
+
# Fetch schema using server_client (supports both name and UUID)
|
|
341
|
+
schema_response = await client.get_schema(ref.identifier)
|
|
342
|
+
|
|
343
|
+
return schema_response.schema_definition
|
|
344
|
+
|
|
345
|
+
def _create_error_from_exception(
|
|
346
|
+
self, pattern: str, exception: Exception
|
|
347
|
+
) -> InjectionError:
|
|
348
|
+
"""Create an InjectionError from an exception.
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
pattern: The pattern that failed
|
|
352
|
+
exception: The exception that was raised
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
InjectionError with appropriate type and message
|
|
356
|
+
"""
|
|
357
|
+
# Handle server_client exceptions
|
|
358
|
+
if isinstance(exception, NotFoundError):
|
|
359
|
+
return InjectionError(
|
|
360
|
+
pattern=pattern,
|
|
361
|
+
error_type="not_found",
|
|
362
|
+
message=f"Fragment or content not found: {exception!s}",
|
|
363
|
+
)
|
|
364
|
+
elif isinstance(exception, ForbiddenError):
|
|
365
|
+
return InjectionError(
|
|
366
|
+
pattern=pattern,
|
|
367
|
+
error_type="permission_denied",
|
|
368
|
+
message=f"Permission denied: {exception!s}",
|
|
369
|
+
)
|
|
370
|
+
elif isinstance(exception, ValidationError):
|
|
371
|
+
return InjectionError(
|
|
372
|
+
pattern=pattern,
|
|
373
|
+
error_type="validation_error",
|
|
374
|
+
message=f"Validation error: {exception!s}",
|
|
375
|
+
)
|
|
376
|
+
elif isinstance(exception, AuthenticationError):
|
|
377
|
+
return InjectionError(
|
|
378
|
+
pattern=pattern,
|
|
379
|
+
error_type="authentication_error",
|
|
380
|
+
message=f"Authentication failed: {exception!s}",
|
|
381
|
+
)
|
|
382
|
+
elif isinstance(exception, ServerError):
|
|
383
|
+
return InjectionError(
|
|
384
|
+
pattern=pattern,
|
|
385
|
+
error_type="api_error",
|
|
386
|
+
message=f"Server error: {exception!s}",
|
|
387
|
+
)
|
|
388
|
+
elif isinstance(exception, ValueError):
|
|
389
|
+
return InjectionError(
|
|
390
|
+
pattern=pattern,
|
|
391
|
+
error_type="invalid_reference",
|
|
392
|
+
message=str(exception),
|
|
393
|
+
)
|
|
394
|
+
else:
|
|
395
|
+
return InjectionError(
|
|
396
|
+
pattern=pattern,
|
|
397
|
+
error_type="unknown_error",
|
|
398
|
+
message=f"Unexpected error: {exception!s}",
|
|
399
|
+
)
|