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
|
File without changes
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"""Injection service for TUI chat.
|
|
2
|
+
|
|
3
|
+
Wraps the shared InjectionResolver and provides caching for autocomplete.
|
|
4
|
+
This service is designed for use with Textual's async workers.
|
|
5
|
+
|
|
6
|
+
Supports the same macro syntax as templates:
|
|
7
|
+
- @fragment(name) - Include a fragment template
|
|
8
|
+
- @text(name) - Insert text from a content part
|
|
9
|
+
- @json(name) - Insert JSON from a content part
|
|
10
|
+
- @schema(name) - Reference a schema definition
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import time
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
|
|
17
|
+
from alloy_runtime_sdk.api_client.client import ApiClient
|
|
18
|
+
|
|
19
|
+
from cli.infrastructure.injection.resolver import (
|
|
20
|
+
InjectionResolver,
|
|
21
|
+
ResolvedMessage,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
# Cache TTL in seconds (5 minutes)
|
|
27
|
+
CACHE_TTL = 300
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class FragmentInfo:
|
|
32
|
+
"""Fragment template information for autocomplete."""
|
|
33
|
+
|
|
34
|
+
id: str
|
|
35
|
+
name: str
|
|
36
|
+
description: str | None
|
|
37
|
+
content_type_id: str
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class ContentPartInfo:
|
|
42
|
+
"""Content part information for autocomplete."""
|
|
43
|
+
|
|
44
|
+
id: str
|
|
45
|
+
content_type_id: str
|
|
46
|
+
content_text: str | None
|
|
47
|
+
has_structured: bool
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class SchemaInfo:
|
|
52
|
+
"""Schema information for autocomplete."""
|
|
53
|
+
|
|
54
|
+
id: str
|
|
55
|
+
schema_name: str
|
|
56
|
+
version: str
|
|
57
|
+
schema_format_id: str
|
|
58
|
+
description: str | None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class InjectionCompletion:
|
|
63
|
+
"""A single injection autocomplete suggestion."""
|
|
64
|
+
|
|
65
|
+
text: str # Full insertion text: @fragment(my-fragment)
|
|
66
|
+
display_name: str # Display name: "my-fragment"
|
|
67
|
+
description: str # Description/type info
|
|
68
|
+
injection_type: str # "fragment", "text", "json", or "schema"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class InjectionService:
|
|
72
|
+
"""Manages injection resolution and caching for TUI.
|
|
73
|
+
|
|
74
|
+
Features:
|
|
75
|
+
- Wraps InjectionResolver for API calls
|
|
76
|
+
- Caches fragments/content/schemas for autocomplete
|
|
77
|
+
- Provides async methods suitable for Textual workers
|
|
78
|
+
- Shares ApiClient with the rest of the TUI
|
|
79
|
+
|
|
80
|
+
Usage:
|
|
81
|
+
service = InjectionService(client)
|
|
82
|
+
await service.refresh_cache()
|
|
83
|
+
|
|
84
|
+
# Check for completions
|
|
85
|
+
completions = service.get_completions("fragment", "my-fra")
|
|
86
|
+
|
|
87
|
+
# Resolve message before sending
|
|
88
|
+
if has_injections(message):
|
|
89
|
+
result = await service.resolve_message(message)
|
|
90
|
+
if result.has_errors:
|
|
91
|
+
# Handle errors
|
|
92
|
+
...
|
|
93
|
+
message = result.text
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(self, client: ApiClient):
|
|
97
|
+
"""Initialize the injection service.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
client: Shared ApiClient instance from the TUI app.
|
|
101
|
+
"""
|
|
102
|
+
self._client = client
|
|
103
|
+
self._resolver = InjectionResolver(client=client)
|
|
104
|
+
|
|
105
|
+
# Autocomplete caches
|
|
106
|
+
self._fragment_cache: list[FragmentInfo] = []
|
|
107
|
+
self._content_cache: list[ContentPartInfo] = []
|
|
108
|
+
self._schema_cache: list[SchemaInfo] = []
|
|
109
|
+
self._cache_expiry: float = 0
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def is_cache_valid(self) -> bool:
|
|
113
|
+
"""Check if the autocomplete cache is still valid."""
|
|
114
|
+
return time.time() < self._cache_expiry
|
|
115
|
+
|
|
116
|
+
async def resolve_message(self, message: str) -> ResolvedMessage:
|
|
117
|
+
"""Resolve all injection patterns in a message.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
message: User message with potential injection patterns.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
ResolvedMessage with resolved text and metadata.
|
|
124
|
+
"""
|
|
125
|
+
return await self._resolver.resolve(message)
|
|
126
|
+
|
|
127
|
+
async def refresh_cache(self) -> None:
|
|
128
|
+
"""Refresh all autocomplete caches from the API.
|
|
129
|
+
|
|
130
|
+
This should be called:
|
|
131
|
+
- On session load/create
|
|
132
|
+
- Periodically in the background
|
|
133
|
+
- When cache expires and user triggers autocomplete
|
|
134
|
+
"""
|
|
135
|
+
try:
|
|
136
|
+
# Fetch all data in parallel
|
|
137
|
+
import asyncio
|
|
138
|
+
|
|
139
|
+
results = await asyncio.gather(
|
|
140
|
+
self._fetch_fragments(),
|
|
141
|
+
self._fetch_content(),
|
|
142
|
+
self._fetch_schemas(),
|
|
143
|
+
return_exceptions=True,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Log any errors but don't fail
|
|
147
|
+
for i, result in enumerate(results):
|
|
148
|
+
if isinstance(result, Exception):
|
|
149
|
+
logger.debug(
|
|
150
|
+
"injection_cache_refresh_partial_failure",
|
|
151
|
+
extra={"index": i, "error": str(result)},
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Update expiry
|
|
155
|
+
self._cache_expiry = time.time() + CACHE_TTL
|
|
156
|
+
|
|
157
|
+
logger.debug(
|
|
158
|
+
"injection_cache_refreshed",
|
|
159
|
+
extra={
|
|
160
|
+
"fragments": len(self._fragment_cache),
|
|
161
|
+
"content": len(self._content_cache),
|
|
162
|
+
"schemas": len(self._schema_cache),
|
|
163
|
+
},
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
except Exception as e:
|
|
167
|
+
logger.debug(
|
|
168
|
+
"injection_cache_refresh_failed",
|
|
169
|
+
extra={"error": str(e)},
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
async def _fetch_fragments(self) -> None:
|
|
173
|
+
"""Fetch fragment templates for autocomplete cache.
|
|
174
|
+
|
|
175
|
+
Only fetches templates with content_type='fragment'.
|
|
176
|
+
"""
|
|
177
|
+
from alloy_runtime_types.enums.template_enums import TemplateContentType
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
response = await self._client.list_templates(
|
|
181
|
+
limit=100, content_type=TemplateContentType.FRAGMENT
|
|
182
|
+
)
|
|
183
|
+
self._fragment_cache = [
|
|
184
|
+
FragmentInfo(
|
|
185
|
+
id=str(t.id),
|
|
186
|
+
name=t.name,
|
|
187
|
+
description=t.description,
|
|
188
|
+
content_type_id=t.content_type_id,
|
|
189
|
+
)
|
|
190
|
+
for t in response.templates
|
|
191
|
+
]
|
|
192
|
+
except Exception as e:
|
|
193
|
+
logger.debug("Failed to fetch fragments: %s", e)
|
|
194
|
+
raise
|
|
195
|
+
|
|
196
|
+
async def _fetch_content(self) -> None:
|
|
197
|
+
"""Fetch content parts for autocomplete cache."""
|
|
198
|
+
try:
|
|
199
|
+
response = await self._client.list_content_parts(limit=100)
|
|
200
|
+
self._content_cache = [
|
|
201
|
+
ContentPartInfo(
|
|
202
|
+
id=str(p.id),
|
|
203
|
+
content_type_id=p.content_type_id,
|
|
204
|
+
content_text=p.content_text,
|
|
205
|
+
has_structured=p.content_structured is not None,
|
|
206
|
+
)
|
|
207
|
+
for p in response.content_parts
|
|
208
|
+
]
|
|
209
|
+
except Exception as e:
|
|
210
|
+
logger.debug("Failed to fetch content parts: %s", e)
|
|
211
|
+
raise
|
|
212
|
+
|
|
213
|
+
async def _fetch_schemas(self) -> None:
|
|
214
|
+
"""Fetch schemas for autocomplete cache."""
|
|
215
|
+
try:
|
|
216
|
+
response = await self._client.list_schemas(limit=100)
|
|
217
|
+
self._schema_cache = [
|
|
218
|
+
SchemaInfo(
|
|
219
|
+
id=str(s.id),
|
|
220
|
+
schema_name=s.schema_name,
|
|
221
|
+
version=str(s.version),
|
|
222
|
+
schema_format_id=s.schema_format_id,
|
|
223
|
+
description=s.description,
|
|
224
|
+
)
|
|
225
|
+
for s in response.schemas
|
|
226
|
+
]
|
|
227
|
+
except Exception as e:
|
|
228
|
+
logger.debug("Failed to fetch schemas: %s", e)
|
|
229
|
+
raise
|
|
230
|
+
|
|
231
|
+
def get_completions(
|
|
232
|
+
self,
|
|
233
|
+
injection_type: str,
|
|
234
|
+
partial_identifier: str = "",
|
|
235
|
+
limit: int = 10,
|
|
236
|
+
) -> list[InjectionCompletion]:
|
|
237
|
+
"""Get autocomplete suggestions for a partial injection.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
injection_type: Type of injection ("fragment", "text", "json", "schema").
|
|
241
|
+
partial_identifier: Partial identifier typed by user.
|
|
242
|
+
limit: Maximum number of suggestions to return.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
List of InjectionCompletion objects.
|
|
246
|
+
"""
|
|
247
|
+
partial_lower = partial_identifier.lower()
|
|
248
|
+
|
|
249
|
+
if injection_type == "fragment":
|
|
250
|
+
return self._get_fragment_completions(partial_lower, limit)
|
|
251
|
+
elif injection_type in ("text", "json"):
|
|
252
|
+
return self._get_content_completions(
|
|
253
|
+
partial_lower, injection_type == "json", limit
|
|
254
|
+
)
|
|
255
|
+
elif injection_type == "schema":
|
|
256
|
+
return self._get_schema_completions(partial_lower, limit)
|
|
257
|
+
else:
|
|
258
|
+
return []
|
|
259
|
+
|
|
260
|
+
def _get_fragment_completions(
|
|
261
|
+
self, partial: str, limit: int
|
|
262
|
+
) -> list[InjectionCompletion]:
|
|
263
|
+
"""Get fragment completions matching the partial identifier."""
|
|
264
|
+
completions: list[InjectionCompletion] = []
|
|
265
|
+
|
|
266
|
+
for fragment in self._fragment_cache:
|
|
267
|
+
name_lower = fragment.name.lower()
|
|
268
|
+
desc_lower = (fragment.description or "").lower()
|
|
269
|
+
|
|
270
|
+
if partial in name_lower or partial in desc_lower:
|
|
271
|
+
# Build the insertion text (no quotes, no variables)
|
|
272
|
+
text = f"@fragment({fragment.name})"
|
|
273
|
+
|
|
274
|
+
# Format description
|
|
275
|
+
description = fragment.description or "no description"
|
|
276
|
+
if len(description) > 30:
|
|
277
|
+
description = description[:27] + "..."
|
|
278
|
+
|
|
279
|
+
completions.append(
|
|
280
|
+
InjectionCompletion(
|
|
281
|
+
text=text,
|
|
282
|
+
display_name=fragment.name,
|
|
283
|
+
description=description,
|
|
284
|
+
injection_type="fragment",
|
|
285
|
+
)
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
if len(completions) >= limit:
|
|
289
|
+
break
|
|
290
|
+
|
|
291
|
+
return completions
|
|
292
|
+
|
|
293
|
+
def _get_content_completions(
|
|
294
|
+
self, partial: str, is_json: bool, limit: int
|
|
295
|
+
) -> list[InjectionCompletion]:
|
|
296
|
+
"""Get content completions matching the partial identifier."""
|
|
297
|
+
completions: list[InjectionCompletion] = []
|
|
298
|
+
|
|
299
|
+
for content in self._content_cache:
|
|
300
|
+
# Match on UUID or content text preview
|
|
301
|
+
id_lower = content.id.lower()
|
|
302
|
+
text_lower = (content.content_text or "").lower()
|
|
303
|
+
|
|
304
|
+
if partial in id_lower or partial in text_lower:
|
|
305
|
+
# Build the insertion text (no quotes)
|
|
306
|
+
if is_json:
|
|
307
|
+
text = f"@json({content.id})"
|
|
308
|
+
format_label = "JSON"
|
|
309
|
+
else:
|
|
310
|
+
text = f"@text({content.id})"
|
|
311
|
+
format_label = "text"
|
|
312
|
+
|
|
313
|
+
# Create preview from content text
|
|
314
|
+
if content.content_text:
|
|
315
|
+
preview = content.content_text.replace("\n", " ").strip()[:30]
|
|
316
|
+
if len(content.content_text) > 30:
|
|
317
|
+
preview += "..."
|
|
318
|
+
else:
|
|
319
|
+
preview = "{structured}" if content.has_structured else "{empty}"
|
|
320
|
+
|
|
321
|
+
description = f"{content.content_type_id} | {format_label}"
|
|
322
|
+
|
|
323
|
+
completions.append(
|
|
324
|
+
InjectionCompletion(
|
|
325
|
+
text=text,
|
|
326
|
+
display_name=preview,
|
|
327
|
+
description=description,
|
|
328
|
+
injection_type="json" if is_json else "text",
|
|
329
|
+
)
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
if len(completions) >= limit:
|
|
333
|
+
break
|
|
334
|
+
|
|
335
|
+
return completions
|
|
336
|
+
|
|
337
|
+
def _get_schema_completions(
|
|
338
|
+
self, partial: str, limit: int
|
|
339
|
+
) -> list[InjectionCompletion]:
|
|
340
|
+
"""Get schema completions matching the partial identifier."""
|
|
341
|
+
completions: list[InjectionCompletion] = []
|
|
342
|
+
|
|
343
|
+
for schema in self._schema_cache:
|
|
344
|
+
name_lower = schema.schema_name.lower()
|
|
345
|
+
desc_lower = (schema.description or "").lower()
|
|
346
|
+
|
|
347
|
+
if partial in name_lower or partial in desc_lower:
|
|
348
|
+
# Build insertion text (no quotes)
|
|
349
|
+
text = f"@schema({schema.schema_name})"
|
|
350
|
+
|
|
351
|
+
# Format description
|
|
352
|
+
desc_preview = (
|
|
353
|
+
schema.description[:20] + "..."
|
|
354
|
+
if schema.description and len(schema.description) > 20
|
|
355
|
+
else schema.description or "no description"
|
|
356
|
+
)
|
|
357
|
+
description = f"{schema.schema_format_id} | {desc_preview}"
|
|
358
|
+
|
|
359
|
+
completions.append(
|
|
360
|
+
InjectionCompletion(
|
|
361
|
+
text=text,
|
|
362
|
+
display_name=schema.schema_name,
|
|
363
|
+
description=description,
|
|
364
|
+
injection_type="schema",
|
|
365
|
+
)
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
if len(completions) >= limit:
|
|
369
|
+
break
|
|
370
|
+
|
|
371
|
+
return completions
|
|
372
|
+
|
|
373
|
+
@property
|
|
374
|
+
def fragments(self) -> list[FragmentInfo]:
|
|
375
|
+
"""Get cached fragments."""
|
|
376
|
+
return self._fragment_cache
|
|
377
|
+
|
|
378
|
+
@property
|
|
379
|
+
def content_parts(self) -> list[ContentPartInfo]:
|
|
380
|
+
"""Get cached content parts."""
|
|
381
|
+
return self._content_cache
|
|
382
|
+
|
|
383
|
+
@property
|
|
384
|
+
def schemas(self) -> list[SchemaInfo]:
|
|
385
|
+
"""Get cached schemas."""
|
|
386
|
+
return self._schema_cache
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""Session name generation service.
|
|
2
|
+
|
|
3
|
+
Automatically generates meaningful session names using a fast, cheap model
|
|
4
|
+
based on the first user message in a chat session.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from alloy_runtime_sdk.api_client.client import ApiClient
|
|
8
|
+
from alloy_runtime_types.dtos.generation import DirectModelSource, GenerateTextRequest
|
|
9
|
+
from alloy_runtime_types.enums.provider import Provider
|
|
10
|
+
from alloy_runtime_sdk.logging.config import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
# Fallback models to try for name generation (fast, cheap models)
|
|
15
|
+
# These are ordered by preference - will try each in sequence until one works
|
|
16
|
+
# Note: Model names must match what's configured in the system
|
|
17
|
+
FALLBACK_MODELS: list[tuple[str, str]] = [
|
|
18
|
+
("google", "gemini-2.0-flash-lite"),
|
|
19
|
+
("google", "gemini-1.5-flash"),
|
|
20
|
+
("google", "gemini-2.0-flash"),
|
|
21
|
+
("openai", "gpt-4o-mini"),
|
|
22
|
+
("openai", "gpt-3.5-turbo"),
|
|
23
|
+
("anthropic", "claude-3-5-haiku-latest"),
|
|
24
|
+
("anthropic", "claude-3-haiku-20240307"),
|
|
25
|
+
("groq", "llama-3.1-8b-instant"),
|
|
26
|
+
("groq", "llama3-8b-8192"),
|
|
27
|
+
("deepseek", "deepseek-chat"),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
# Prompt for generating session names
|
|
31
|
+
NAME_GENERATION_PROMPT = """Generate a short, descriptive title (3-6 words) for a chat conversation based on the user's first message. The title should capture the main topic or intent.
|
|
32
|
+
|
|
33
|
+
Rules:
|
|
34
|
+
- Return ONLY the title, nothing else
|
|
35
|
+
- No quotes, punctuation at the end, or explanations
|
|
36
|
+
- Use title case
|
|
37
|
+
- Be specific but concise
|
|
38
|
+
- If the message is a greeting or very generic, create a generic but friendly title
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
- "Help me write a Python script" -> "Python Script Assistance"
|
|
42
|
+
- "What's the weather like?" -> "Weather Information Request"
|
|
43
|
+
- "Hello!" -> "New Conversation"
|
|
44
|
+
- "Debug this React component" -> "React Component Debugging"
|
|
45
|
+
|
|
46
|
+
User's first message:
|
|
47
|
+
{message}
|
|
48
|
+
|
|
49
|
+
Title:"""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class SessionNameGenerator:
|
|
53
|
+
"""Generates session names using a fast, cheap model.
|
|
54
|
+
|
|
55
|
+
Tries multiple fallback models to ensure name generation works even if
|
|
56
|
+
some providers are not configured.
|
|
57
|
+
|
|
58
|
+
Usage:
|
|
59
|
+
generator = SessionNameGenerator()
|
|
60
|
+
name = await generator.generate_name(client, "Help me write a function")
|
|
61
|
+
# Returns something like "Function Writing Help"
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
# Cheap models that are OK to use for naming (won't cost much)
|
|
65
|
+
_CHEAP_MODEL_PATTERNS = [
|
|
66
|
+
"flash",
|
|
67
|
+
"mini",
|
|
68
|
+
"nano",
|
|
69
|
+
"haiku",
|
|
70
|
+
"instant",
|
|
71
|
+
"3.5-turbo",
|
|
72
|
+
"8b",
|
|
73
|
+
"7b",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
def __init__(
|
|
77
|
+
self,
|
|
78
|
+
fallback_models: list[tuple[str, str]] | None = None,
|
|
79
|
+
max_message_length: int = 500,
|
|
80
|
+
) -> None:
|
|
81
|
+
"""Initialize the session name generator.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
fallback_models: List of (provider_key, model_name) tuples to try.
|
|
85
|
+
Defaults to FALLBACK_MODELS.
|
|
86
|
+
max_message_length: Maximum length of user message to include in prompt.
|
|
87
|
+
"""
|
|
88
|
+
self._fallback_models = fallback_models or FALLBACK_MODELS
|
|
89
|
+
self._max_message_length = max_message_length
|
|
90
|
+
|
|
91
|
+
def _is_cheap_model(self, model_name: str) -> bool:
|
|
92
|
+
"""Check if a model is considered cheap enough for naming tasks."""
|
|
93
|
+
model_lower = model_name.lower()
|
|
94
|
+
return any(pattern in model_lower for pattern in self._CHEAP_MODEL_PATTERNS)
|
|
95
|
+
|
|
96
|
+
async def generate_name(
|
|
97
|
+
self,
|
|
98
|
+
client: ApiClient,
|
|
99
|
+
first_message: str,
|
|
100
|
+
session_provider_key: str | None = None,
|
|
101
|
+
session_model_name: str | None = None,
|
|
102
|
+
) -> str | None:
|
|
103
|
+
"""Generate a session name based on the first message.
|
|
104
|
+
|
|
105
|
+
Tries each fallback model in sequence until one succeeds.
|
|
106
|
+
If a session model is provided and it's cheap, tries that first.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
client: ApiClient for making API calls.
|
|
110
|
+
first_message: The user's first message in the session.
|
|
111
|
+
session_provider_key: Optional provider key from the session (tried first if cheap).
|
|
112
|
+
session_model_name: Optional model name from the session (tried first if cheap).
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Generated session name (3-6 words), or None if all models fail.
|
|
116
|
+
"""
|
|
117
|
+
logger.info(
|
|
118
|
+
"session_name_generator_starting",
|
|
119
|
+
first_message_length=len(first_message),
|
|
120
|
+
session_provider_key=session_provider_key,
|
|
121
|
+
session_model_name=session_model_name,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Truncate message if too long
|
|
125
|
+
truncated_message = first_message[: self._max_message_length]
|
|
126
|
+
if len(first_message) > self._max_message_length:
|
|
127
|
+
truncated_message += "..."
|
|
128
|
+
|
|
129
|
+
prompt = NAME_GENERATION_PROMPT.format(message=truncated_message)
|
|
130
|
+
|
|
131
|
+
# Build list of models to try
|
|
132
|
+
models_to_try: list[tuple[str, str]] = []
|
|
133
|
+
|
|
134
|
+
# If session has a cheap model, try it first (it's likely configured)
|
|
135
|
+
if (
|
|
136
|
+
session_provider_key
|
|
137
|
+
and session_model_name
|
|
138
|
+
and self._is_cheap_model(session_model_name)
|
|
139
|
+
):
|
|
140
|
+
models_to_try.append((session_provider_key, session_model_name))
|
|
141
|
+
logger.info(
|
|
142
|
+
"session_name_using_session_model_first",
|
|
143
|
+
provider=session_provider_key,
|
|
144
|
+
model=session_model_name,
|
|
145
|
+
)
|
|
146
|
+
else:
|
|
147
|
+
logger.info(
|
|
148
|
+
"session_name_not_using_session_model",
|
|
149
|
+
session_provider_key=session_provider_key,
|
|
150
|
+
session_model_name=session_model_name,
|
|
151
|
+
is_cheap=self._is_cheap_model(session_model_name)
|
|
152
|
+
if session_model_name
|
|
153
|
+
else None,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Add fallback models
|
|
157
|
+
models_to_try.extend(self._fallback_models)
|
|
158
|
+
logger.info(
|
|
159
|
+
"session_name_models_to_try",
|
|
160
|
+
total_models=len(models_to_try),
|
|
161
|
+
fallback_models=len(self._fallback_models),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
for idx, (provider_key, model_name) in enumerate(models_to_try):
|
|
165
|
+
try:
|
|
166
|
+
logger.info(
|
|
167
|
+
"session_name_generation_attempt",
|
|
168
|
+
attempt_number=idx + 1,
|
|
169
|
+
provider=provider_key,
|
|
170
|
+
model=model_name,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
model_source = DirectModelSource(
|
|
174
|
+
provider_key=Provider(provider_key),
|
|
175
|
+
provider_model_name=model_name,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
request = GenerateTextRequest(
|
|
179
|
+
model_source=model_source,
|
|
180
|
+
user_message=prompt,
|
|
181
|
+
stream=False,
|
|
182
|
+
tags=["system.session_naming"],
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
response = await client.generate_text(request)
|
|
186
|
+
logger.info(
|
|
187
|
+
"session_name_generation_response",
|
|
188
|
+
provider=provider_key,
|
|
189
|
+
model=model_name,
|
|
190
|
+
raw_output=response.output_text[:100]
|
|
191
|
+
if response.output_text
|
|
192
|
+
else None,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
generated_name = self._clean_name(response.output_text)
|
|
196
|
+
|
|
197
|
+
if generated_name:
|
|
198
|
+
logger.info(
|
|
199
|
+
"session_name_generated_success",
|
|
200
|
+
provider=provider_key,
|
|
201
|
+
model=model_name,
|
|
202
|
+
generated_name=generated_name,
|
|
203
|
+
)
|
|
204
|
+
return generated_name
|
|
205
|
+
else:
|
|
206
|
+
logger.warning(
|
|
207
|
+
"session_name_cleaned_to_empty",
|
|
208
|
+
provider=provider_key,
|
|
209
|
+
model=model_name,
|
|
210
|
+
raw_output=response.output_text,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
except Exception as e:
|
|
214
|
+
logger.info(
|
|
215
|
+
"session_name_generation_model_failed",
|
|
216
|
+
attempt_number=idx + 1,
|
|
217
|
+
provider=provider_key,
|
|
218
|
+
model=model_name,
|
|
219
|
+
error=str(e),
|
|
220
|
+
error_type=type(e).__name__,
|
|
221
|
+
)
|
|
222
|
+
continue
|
|
223
|
+
|
|
224
|
+
logger.warning(
|
|
225
|
+
"session_name_generation_all_models_failed",
|
|
226
|
+
models_tried=len(models_to_try),
|
|
227
|
+
)
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
def _clean_name(self, raw_name: str) -> str | None:
|
|
231
|
+
"""Clean and validate the generated name.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
raw_name: Raw output from the model.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Cleaned name (max 100 chars), or None if invalid.
|
|
238
|
+
"""
|
|
239
|
+
# Strip whitespace and quotes
|
|
240
|
+
name = raw_name.strip().strip("\"'")
|
|
241
|
+
|
|
242
|
+
# Remove common prefixes the model might add
|
|
243
|
+
prefixes_to_remove = ["Title:", "title:", "Name:", "name:"]
|
|
244
|
+
for prefix in prefixes_to_remove:
|
|
245
|
+
if name.startswith(prefix):
|
|
246
|
+
name = name[len(prefix) :].strip()
|
|
247
|
+
|
|
248
|
+
# Validate length
|
|
249
|
+
if not name or len(name) < 2:
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
# Truncate if too long
|
|
253
|
+
if len(name) > 100:
|
|
254
|
+
name = name[:97] + "..."
|
|
255
|
+
|
|
256
|
+
return name
|