precis-cli 0.1.8__tar.gz → 0.1.9__tar.gz
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.
- {precis_cli-0.1.8/precis_cli.egg-info → precis_cli-0.1.9}/PKG-INFO +1 -1
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/models/projects.py +12 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/ai/models.py +4 -2
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/ai/stream.py +64 -19
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/projects/__init__.py +2 -1
- precis_cli-0.1.9/app/api/routers/projects/check.py +62 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/ai/agent_interaction.py +19 -2
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/ai/executor.py +32 -19
- precis_cli-0.1.9/app/cli/shell/commands/ai/stream_renderer.py +188 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/mcp_server.py +10 -33
- precis_cli-0.1.9/app/shared/core/app_version.py +100 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/specs/json_source.py +1 -2
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/loader/loader_parts/embedded_constraints.py +87 -41
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/loader/loader_parts/main.py +3 -3
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/schema/types_parts/column_utils.py +37 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/schema/types_parts/source.py +2 -2
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/schema/types_parts/source_options.py +3 -4
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/constraints/range.py +2 -2
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/chat_tools/__init__.py +9 -1
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/chat_tools/apply_actions.py +148 -12
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/chat_tools/ask_user.py +1 -1
- precis_cli-0.1.9/app/shared/services/ai/agent/chat_tools/infer_schema.py +175 -0
- precis_cli-0.1.9/app/shared/services/ai/agent/chat_tools/list_data_files.py +222 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/chat_tools/schemas.py +13 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/chat_tools/validate_table.py +3 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/memory.py +42 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/tool_registry.py +57 -2
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/tools/config_validate.py +68 -5
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/types.py +3 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/chat_agent_runner.py +83 -25
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/chat_orchestrator.py +32 -11
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/streaming/orchestrator.py +11 -5
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/streaming/pending_interaction_store.py +113 -20
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/utils.py +75 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/actions/action_handlers.py +1 -1
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/actions/action_processor.py +26 -6
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/actions/regex_handlers.py +5 -2
- precis_cli-0.1.9/app/shared/services/llm/actions/registry.py +675 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/actions/schema_handlers.py +80 -10
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/actions/transform_handlers.py +14 -4
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/chat/chat_system_prompt.py +13 -19
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/constraints/constraint_builder.py +10 -4
- precis_cli-0.1.9/app/shared/services/llm/constraints/frontend_instructions.py +348 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/generation/agent_wiring.py +1 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/generation/config_builder.py +298 -18
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/generation/prompt_builder.py +13 -8
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/generation/service.py +1 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/providers/openai.py +30 -15
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/schema_resolver.py +32 -6
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/validate_executor.py +37 -3
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/yaml_io.py +33 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/schema_inference.py +13 -1
- {precis_cli-0.1.8 → precis_cli-0.1.9/precis_cli.egg-info}/PKG-INFO +1 -1
- {precis_cli-0.1.8 → precis_cli-0.1.9}/precis_cli.egg-info/SOURCES.txt +4 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/pyproject.toml +1 -1
- precis_cli-0.1.8/app/shared/core/app_version.py +0 -59
- precis_cli-0.1.8/app/shared/services/llm/actions/registry.py +0 -340
- precis_cli-0.1.8/app/shared/services/llm/constraints/frontend_instructions.py +0 -333
- {precis_cli-0.1.8 → precis_cli-0.1.9}/MANIFEST.in +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/PYPI_README.md +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/README.md +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/dependencies.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/main.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/middleware/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/middleware/exception_handler.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/middleware/request_logging.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/middleware/token_auth.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/models/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/models/connection_rules.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/models/files.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/models/full_validation.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/models/project.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/models/schema.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/models/v2_responses.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/models/validation.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/models/workspace.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/ai/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/ai/chat.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/ai/generate.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/ai/hardware.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/ai/jobs.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/ai/migrate.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/ai/ollama.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/ai/providers.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/ai/router.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/ai/utils.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/core/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/core/connection_rules.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/core/data_sources.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/core/regex.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/core/reporting.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/files/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/files/ops.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/files/transfer.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/preview/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/preview/content_mode.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/preview/header_row.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/preview/models.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/preview/path_mode.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/preview/router.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/project/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/project/base.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/project/constraint.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/project/full_config.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/project/full_config_writer.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/project/helpers.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/project/inspection_fix.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/project/manifest.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/project/models.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/project/pattern.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/project/regex.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/project/scanner.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/project/schema.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/project/schema_helpers.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/project/settings.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/project/template.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/project/validation.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/project/view.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/project/workspaces.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/projects/create.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/projects/open.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/projects/scan.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/validation/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/validation/common.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/validation/content_mode.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/validation/history.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/validation/inline_mode.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/validation/path_mode.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/routers/validation/router.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/services/full_validation_response_builder.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/services/io_error_messages.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/api/services/preview_service.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/__main__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shared_services/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shared_services/config_ops.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shared_services/generation_ops.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shared_services/project_ops.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/ai/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/ai/chat.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/ai/delete.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/ai/diff.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/ai/display.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/ai/executor_utils.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/ai/generate.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/ai/interaction.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/ai/migrate.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/ai/resolver.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/ai/status.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/ai/switch.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/ai/utils.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/base.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/config/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/config/base.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/config/check.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/config/edit.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/config/get.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/config/init.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/config/inspect.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/config/list.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/config/set.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/config/show.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/exit.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/help.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/infer_schema.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/open.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/project.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/provider.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/system.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/commands/validate.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/completer.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/config_storage.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/exceptions.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/formatter.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/interactive_menu.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/main.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/shell/parser.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/cli/start.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/config/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/config/server.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/loader.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/loaders/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/loaders/base.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/loaders/converter.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/loaders/csv_loader.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/loaders/excel_loader.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/loaders/extractor.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/loaders/json_loader.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/loaders/registry.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/loaders/sql_loader.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/loaders/strategies/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/loaders/strategies/array_parser.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/loaders/strategies/lines_parser.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/loaders/strategies/object_parser.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/schema_info.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/specs/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/specs/base.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/specs/csv_source.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/specs/excel_source.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/specs/file_base.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/data_source/specs/sql_source.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/encoding.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/io/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/io/yaml.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/manifest_schema/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/manifest_schema/version.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/patterns/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/patterns/loader.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/patterns/writer.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/constraint/builders/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/constraint/builders/base.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/constraint/builders/composite.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/constraint/builders/conditional.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/constraint/builders/foreign_key.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/constraint/builders/registry.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/constraint/builders/scripted.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/constraint/builders/single_column.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/constraint/builders/unique.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/constraint/factory.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/constraint/reader.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/constraint/registry.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/constraint/types/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/constraint/types/constraint_file.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/constraint/types/refs.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/constraint/types.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/constraint/writer.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/loader/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/loader/loader.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/loader/loader_parts/config_inspector.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/loader/loader_parts/file_loaders.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/loader/loader_parts/inspection_ids.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/loader/loader_parts/inspector_helpers.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/loader/loader_parts/inspector_id_checks.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/loader/loader_parts/inspector_reference_checks.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/loader/loader_parts/inspector_uniqueness_checks.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/loader/loader_parts/loading_error_messages.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/loader/loader_parts/path_validation.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/loader/loader_parts/runtime.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/loader/types.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/manifest/coverage.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/manifest/reader.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/manifest/types.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/manifest/types_parts/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/manifest/types_parts/constants.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/manifest/types_parts/data_source.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/manifest/types_parts/info.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/manifest/types_parts/manifest.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/manifest/types_parts/refs.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/manifest/types_parts/settings.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/manifest/types_parts/settings_file_processing.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/manifest/types_parts/settings_script_security.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/manifest/types_parts/settings_validation.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/manifest/types_parts/template.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/manifest/writer.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/manual_data/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/manual_data/types.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/regex/reader.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/regex/types.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/regex/writer.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/schema/reader.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/schema/types.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/schema/types_parts/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/schema/types_parts/column.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/schema/types_parts/constraint.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/schema/types_parts/schema_id.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/schema/types_parts/table.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/schema/writer.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/schema_ref_check.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/template/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/template/expander.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/template/reader.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/template/types.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/transform/reader.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/transform/types.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/project/transform/writer.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/pydantic_messages.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/reporter/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/reporter/reporter.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/reporter/reporters/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/reporter/reporters/base.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/reporter/reporters/dingtalk_app_reporter.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/reporter/reporters/email_reporter.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/reporter/reporters/feishu_app_reporter.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/reporter/reporters/local_file_reporter.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/reporter/reporters/wecom_app_reporter.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/utils/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/utils/path_utils.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/core/utils/regex_extract.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/constraints/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/constraints/allowed_values.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/constraints/base.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/constraints/charset.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/constraints/composite.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/constraints/condition_registry.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/constraints/conditional.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/constraints/date_logic.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/constraints/foreign_key.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/constraints/key_normalization.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/constraints/not_null.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/constraints/regex.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/constraints/scripted.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/constraints/unique.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/data_engine.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/data_types.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/data_types_parts/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/data_types_parts/base.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/data_types_parts/composite.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/data_types_parts/expression.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/data_types_parts/extracted.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/data_types_parts/json_types.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/data_types_parts/scalars.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/data_types_parts/sequence.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/dataset_schema.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/eval_sandbox.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/expression_system.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/regex_flags.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/schema/builder.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/schema/models.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/aggregate.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/base.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/cast_type.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/concat.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/conditional_assign.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/date_format.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/digits.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/drop_duplicates.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/fill_na.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/filter_rows.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/lookup.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/lower_case.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/map_value.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/math_expr.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/modulo.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/regex_extract.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/registry.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/replace.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/sort_rows.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/string_split.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/strip.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/substring.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/upper_case.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/transforms/weighted_sum.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/domain/validation_constraints.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/chat_tools/read_canvas.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/chat_tools/read_project.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/chat_tools/read_table.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/executor.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/planner.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/tools/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/tools/config_generate.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/tools/config_refine.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/tools/merge_results.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/tools/plan_chunks.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/agent/tools/script_parse.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/failure_messages.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/job_storage.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/migrate_service.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/streaming/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/streaming/event_journal.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/streaming/sse_response.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/ai/streaming/types.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/diff/config_diff.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/hardware.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/actions/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/actions/_canvas_validator.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/actions/_constraint_validator.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/actions/_regex_validator.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/actions/_schema_validator.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/actions/_settings_validator.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/actions/_transform_validator.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/actions/action_parser.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/actions/action_validator.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/actions/diff_compute.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/actions/settings_handlers.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/actions/specs.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/actions/validation_types.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/cache/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/cache/response_cache.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/chat/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/chat/response_parser.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/config/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/config/crypto.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/config/loader.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/config/models.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/config/presets.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/config_generator.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/constraints/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/constraints/constraint_deletion.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/constraints/constraint_id.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/constraints/inline_batch.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/discovery/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/discovery/scanner.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/generation/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/generation/errors.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/generation/existing_config.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/generation/profiler.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/generation/response_parser.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/providers/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/providers/base.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/providers/cached_provider.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/providers/fake.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/providers/ollama.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/providers/registry.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/llm/suggestion_utils.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/preview/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/preview/loader.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/preview/path_validation.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/project_loader.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/schema_runtime_builder.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/chunked_loader.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/dag/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/dag/builder.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/dag/executor.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/dag/sorter.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/data_loader.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/engine.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/executor.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/extractors.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/history.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/json_payload.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/loader.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/memory_monitor.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/postprocess.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/progress.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/report_export.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/resolver.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/service.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/types.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/validators/__init__.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/validators/adapter.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/validators/base.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/validators/composite.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/shared/services/validation/validators/date_logic.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/app/start_server.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/precis_cli.egg-info/dependency_links.txt +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/precis_cli.egg-info/entry_points.txt +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/precis_cli.egg-info/requires.txt +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/precis_cli.egg-info/top_level.txt +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/setup.cfg +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/tests/test_local_file_reporter.py +0 -0
- {precis_cli-0.1.8 → precis_cli-0.1.9}/tests/test_schema_builder.py +0 -0
|
@@ -78,3 +78,15 @@ class CloseProjectResponse(BaseModel):
|
|
|
78
78
|
"""关闭项目的响应。"""
|
|
79
79
|
|
|
80
80
|
success: bool
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class CheckProjectResponse(BaseModel):
|
|
84
|
+
"""项目探测端点(GET /projects/check)的响应。
|
|
85
|
+
|
|
86
|
+
设计约束:对合法输入一律 200,探测结果用布尔字段表达——前端「智能打开」
|
|
87
|
+
用它预判目录是否为项目根,避免探测请求在浏览器控制台留下 404 红字。
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
path: str
|
|
91
|
+
dir_exists: bool
|
|
92
|
+
is_project: bool
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
manifest: Optional[dict[str, Any]] = None
|
|
40
40
|
"""
|
|
41
41
|
|
|
42
|
-
from typing import Any
|
|
42
|
+
from typing import Any, Literal
|
|
43
43
|
|
|
44
44
|
from pydantic import BaseModel, ConfigDict, Field
|
|
45
45
|
|
|
@@ -110,7 +110,9 @@ class AiChatRequest(BaseModel):
|
|
|
110
110
|
class AiChatConfirmRequest(BaseModel):
|
|
111
111
|
"""AI Chat apply_actions 确认/拒绝请求"""
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
# Literal 枚举:非法值(如 "yes"/"ok")由 Pydantic 自动 422 并回显合法值集合,
|
|
114
|
+
# 不再被静默当作 reject 处理
|
|
115
|
+
decision: Literal["confirm", "reject"] = Field(..., description="confirm(确认并落盘)或 reject(拒绝,不落盘)")
|
|
114
116
|
apply_id: str | None = Field(
|
|
115
117
|
default=None,
|
|
116
118
|
description="本次 apply 的 ID({job_id}#{seq})。为空时回退到该 job 当前唯一挂起项。",
|
|
@@ -27,7 +27,12 @@
|
|
|
27
27
|
- 取消信号: 内存中 _cancel_events 字典(job_id → asyncio.Event)
|
|
28
28
|
- Last-Event-ID: 仅作为本连接内 journal 回放的起始游标(§2.8);不支持跨连接断线续传——
|
|
29
29
|
每次请求都新建 job_id 与 journal,重连等价于重新发起会话(真续传需 job 持久化+按 job_id 重连端点,属未来 feature)
|
|
30
|
-
-
|
|
30
|
+
- SSE 断开宽限期: 客户端断开不取消后台任务(网络抖动/休眠/组件卸载等瞬断不应软取消
|
|
31
|
+
整个 job);任务继续运行至终态事件或挂起确认自然超时,EventJournal 持续落盘。
|
|
32
|
+
断开仅登记"客户端失联",让等待中的 apply/ask 超时兜底按"连接中断"回灌文案;
|
|
33
|
+
显式取消走 /jobs/{job_id}/cancel 端点(行为不变)
|
|
34
|
+
- 资源清理: orchestrator 结束后注销 cancel_event,防止内存泄漏——
|
|
35
|
+
cancel_event 的存续期等于后台 task 生命周期(宽限期内 task 未结束则保留,属在用而非泄漏)
|
|
31
36
|
"""
|
|
32
37
|
|
|
33
38
|
from __future__ import annotations
|
|
@@ -67,6 +72,12 @@ _cancel_events_lock = threading.Lock()
|
|
|
67
72
|
# task 完成后通过 add_done_callback 自动从集合移除
|
|
68
73
|
_background_tasks: set[asyncio.Task[None]] = set()
|
|
69
74
|
|
|
75
|
+
# 实时事件队列容量上限:journal 是权威全量记录(终止事件携带完整快照兜底),
|
|
76
|
+
# 丢实时帧只影响投递即时性、无正确性影响;而 SSE 断开宽限期内(挂起确认 300s × N +
|
|
77
|
+
# LLM 流式)队列无人消费,无界队列会让 delta/tool 事件持续内存积压。
|
|
78
|
+
# 有界后 orchestrator.emit 的 QueueFull 丢弃守卫被激活:满员丢实时帧、journal 照常落盘
|
|
79
|
+
_EVENT_QUEUE_MAXSIZE = 1000
|
|
80
|
+
|
|
70
81
|
# journal 存储目录基础(按项目 config_path 分片),复用 AgentJobStorage 的 .precis 约定
|
|
71
82
|
# 实际路径在端点中按 project_path 拼接
|
|
72
83
|
|
|
@@ -89,6 +100,41 @@ def _journal_dir_for(project_path: str | None) -> str:
|
|
|
89
100
|
return os.path.join(os.path.expanduser("~"), ".precis", "stream_jobs")
|
|
90
101
|
|
|
91
102
|
|
|
103
|
+
async def _sse_frames_with_disconnect_grace(
|
|
104
|
+
job_id: str,
|
|
105
|
+
journal: EventJournal,
|
|
106
|
+
last_event_id: int,
|
|
107
|
+
event_queue: asyncio.Queue[dict[str, Any]],
|
|
108
|
+
) -> AsyncIterator[str]:
|
|
109
|
+
"""@methoddesc SSE 帧生成器(断开宽限期语义)
|
|
110
|
+
|
|
111
|
+
与旧实现的差异:客户端断开时不再 cancel_event.set()——瞬断(网络抖动/系统休眠/
|
|
112
|
+
IDE↔Agent 模式切换卸载组件)不应软取消整个后台任务。任务继续运行至终态事件
|
|
113
|
+
(completed/error/cancelled)或挂起确认自然超时(300s),期间 EventJournal 持续
|
|
114
|
+
落盘(重连回放/事后审计的前提),confirm/respond 端点仍可决议挂起交互。
|
|
115
|
+
|
|
116
|
+
断开时只做一件事:登记该 job 客户端失联(mark_job_client_gone),使等待中的
|
|
117
|
+
apply 确认/ask 提问在超时兜底时按"连接中断"(decision=disconnected)而非
|
|
118
|
+
"用户拒绝/跳过"回灌文案。显式取消走 /jobs/{job_id}/cancel 端点,行为不变。
|
|
119
|
+
"""
|
|
120
|
+
completed_normally = False
|
|
121
|
+
try:
|
|
122
|
+
async for frame in sse_event_stream(
|
|
123
|
+
journal=journal,
|
|
124
|
+
last_event_id=last_event_id,
|
|
125
|
+
event_queue=event_queue,
|
|
126
|
+
):
|
|
127
|
+
yield frame
|
|
128
|
+
# 流自然耗尽 == 已投递终态事件(sse_event_stream 仅在终态后结束):
|
|
129
|
+
# 任务已结束、挂起交互已决议,无需登记失联
|
|
130
|
+
completed_normally = True
|
|
131
|
+
finally:
|
|
132
|
+
# 仅异常终止(客户端断开触发 GeneratorExit/CancelledError、流内部异常)
|
|
133
|
+
# 才登记失联;正常收尾不登记,避免给每个已完成 job 留下失联残影
|
|
134
|
+
if not completed_normally:
|
|
135
|
+
get_global_pending_interaction_store().mark_job_client_gone(job_id)
|
|
136
|
+
|
|
137
|
+
|
|
92
138
|
@router.post("/chat/stream", summary="AI 聊天流式接口(SSE)")
|
|
93
139
|
async def chat_stream(
|
|
94
140
|
request: AiChatRequest,
|
|
@@ -148,8 +194,10 @@ async def chat_stream(
|
|
|
148
194
|
canvas_nodes = [node.model_dump() for node in request.context.canvasNodes]
|
|
149
195
|
history = [{"role": h.role, "content": h.content} for h in (request.history or [])]
|
|
150
196
|
|
|
151
|
-
# 实时事件队列: orchestrator emit 时推入, sse_event_stream
|
|
152
|
-
|
|
197
|
+
# 实时事件队列: orchestrator emit 时推入, sse_event_stream 消费。
|
|
198
|
+
# 有界(_EVENT_QUEUE_MAXSIZE):宽限期内无人消费时不内存积压,
|
|
199
|
+
# 满员由 emit 侧丢弃实时帧(journal 仍是权威全量记录)
|
|
200
|
+
event_queue: asyncio.Queue[dict[str, Any]] = asyncio.Queue(maxsize=_EVENT_QUEUE_MAXSIZE)
|
|
153
201
|
|
|
154
202
|
orchestrator = StreamingOrchestrator(
|
|
155
203
|
job_id=job_id,
|
|
@@ -177,21 +225,17 @@ async def chat_stream(
|
|
|
177
225
|
_background_tasks.add(task)
|
|
178
226
|
task.add_done_callback(_background_tasks.discard)
|
|
179
227
|
|
|
180
|
-
# 返回 SSE 流: 先回放 journal(本连接内,自 last_event_id 起),
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
# 这让 executor 在下一个取消检查点中断,避免后台 task 泄漏
|
|
192
|
-
cancel_event.set()
|
|
193
|
-
|
|
194
|
-
return StreamingResponse(_sse_generator(), media_type="text/event-stream")
|
|
228
|
+
# 返回 SSE 流: 先回放 journal(本连接内,自 last_event_id 起), 再实时推送队列。
|
|
229
|
+
# 生成器含断开宽限期语义:断开不取消后台任务(见 _sse_frames_with_disconnect_grace)
|
|
230
|
+
return StreamingResponse(
|
|
231
|
+
_sse_frames_with_disconnect_grace(
|
|
232
|
+
job_id=job_id,
|
|
233
|
+
journal=journal,
|
|
234
|
+
last_event_id=last_event_id,
|
|
235
|
+
event_queue=event_queue,
|
|
236
|
+
),
|
|
237
|
+
media_type="text/event-stream",
|
|
238
|
+
)
|
|
195
239
|
|
|
196
240
|
|
|
197
241
|
@router.post("/jobs/{job_id}/cancel", summary="取消正在运行的 AI job(软取消)")
|
|
@@ -234,7 +278,8 @@ async def confirm_apply(job_id: str, request: AiChatConfirmRequest) -> dict[str,
|
|
|
234
278
|
|
|
235
279
|
参数:
|
|
236
280
|
job_id: 任务 ID
|
|
237
|
-
request: 含 decision 字段("confirm" 或 "reject"
|
|
281
|
+
request: 含 decision 字段("confirm" 或 "reject";非法值由 Pydantic 校验
|
|
282
|
+
返回 422 并回显合法值集合)
|
|
238
283
|
|
|
239
284
|
返回:
|
|
240
285
|
{"status": "resolved", "decision": "confirm"} 或 404
|
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
|
|
18
18
|
from fastapi import APIRouter
|
|
19
19
|
|
|
20
|
-
from . import create, open, scan
|
|
20
|
+
from . import check, create, open, scan
|
|
21
21
|
|
|
22
22
|
router = APIRouter(prefix="/api/latest/projects", tags=["Projects"])
|
|
23
23
|
router.include_router(scan.router)
|
|
24
24
|
router.include_router(open.router)
|
|
25
25
|
router.include_router(create.router)
|
|
26
|
+
router.include_router(check.router)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
"""
|
|
17
|
+
@fileoverview 项目探测路由:只读判断目录是否为合法 Precis 项目根
|
|
18
|
+
|
|
19
|
+
功能概述:
|
|
20
|
+
- GET /api/latest/projects/check?path=<绝对路径>
|
|
21
|
+
- 供前端「智能打开」(useSmartProjectOpen)在引导就地新建前探测目录
|
|
22
|
+
|
|
23
|
+
架构设计:
|
|
24
|
+
- 与 open.py/create.py 对称,但**永不 404**:空目录/不存在的目录都用布尔
|
|
25
|
+
字段表达(dir_exists/is_project),浏览器控制台不会因探测留下 404 红字;
|
|
26
|
+
仅非法输入(相对路径/路径穿越)返回 400
|
|
27
|
+
- 路径经 query 参数传递(UTF-8 自动编解码),无 X-Project-Config-Path
|
|
28
|
+
header 的 latin-1 编码问题
|
|
29
|
+
- 不经 get_project_config_path 依赖(该依赖对缺 manifest 的目录抛 404,
|
|
30
|
+
正是本端点要避免的语义)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import os
|
|
36
|
+
|
|
37
|
+
from fastapi import APIRouter, HTTPException, Query
|
|
38
|
+
|
|
39
|
+
from app.api.models.projects import CheckProjectResponse
|
|
40
|
+
|
|
41
|
+
router = APIRouter(prefix="", tags=["Projects-Check"])
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@router.get(
|
|
45
|
+
"/check",
|
|
46
|
+
response_model=CheckProjectResponse,
|
|
47
|
+
summary="探测目录是否为 Precis 项目(永不 404)",
|
|
48
|
+
)
|
|
49
|
+
def check_project(path: str = Query(..., description="待探测的目录绝对路径")) -> CheckProjectResponse:
|
|
50
|
+
"""只读探测目录状态,不创建、不修改任何文件,不改变"当前项目"。"""
|
|
51
|
+
# 输入校验与 get_project_config_path 同规:先拒绝相对路径(Windows 下
|
|
52
|
+
# abspath 会把相对路径解析成绝对路径,必须先判 isabs),再防路径穿越
|
|
53
|
+
if not os.path.isabs(path):
|
|
54
|
+
raise HTTPException(status_code=400, detail="path 必须是一个绝对路径。")
|
|
55
|
+
|
|
56
|
+
normalized = os.path.abspath(os.path.normpath(path))
|
|
57
|
+
if ".." in normalized.split(os.sep):
|
|
58
|
+
raise HTTPException(status_code=400, detail="path 不允许包含 .. 目录穿越。")
|
|
59
|
+
|
|
60
|
+
dir_exists = os.path.isdir(normalized)
|
|
61
|
+
is_project = os.path.isfile(os.path.join(normalized, "project.precis.yaml"))
|
|
62
|
+
return CheckProjectResponse(path=normalized, dir_exists=dir_exists, is_project=is_project)
|
|
@@ -157,11 +157,27 @@ def _print_ask_question(payload: dict[str, Any]) -> None:
|
|
|
157
157
|
options = payload.get("options")
|
|
158
158
|
if isinstance(options, list) and options:
|
|
159
159
|
for idx, opt in enumerate(options, start=1):
|
|
160
|
-
|
|
160
|
+
_, text = _split_option(opt)
|
|
161
|
+
print(f" {idx}. {text}")
|
|
161
162
|
if payload.get("optional"):
|
|
162
163
|
print(Formatter.info(" (直接回车跳过此问题)"))
|
|
163
164
|
|
|
164
165
|
|
|
166
|
+
def _split_option(opt: Any) -> tuple[str, str]:
|
|
167
|
+
"""解析单个选项为 (value, 展示文本)。
|
|
168
|
+
|
|
169
|
+
ask_user 工具 schema 声明 options 为 {label, value, description?} 对象;
|
|
170
|
+
纯字符串形态(历史/测试用例)兼容处理,value 即字符串本身。
|
|
171
|
+
"""
|
|
172
|
+
if isinstance(opt, dict):
|
|
173
|
+
value = str(opt.get("value", opt.get("label", "")))
|
|
174
|
+
label = str(opt.get("label", value))
|
|
175
|
+
desc = str(opt.get("description") or "")
|
|
176
|
+
text = f"{label} — {desc}" if desc else label
|
|
177
|
+
return value, text
|
|
178
|
+
return str(opt), str(opt)
|
|
179
|
+
|
|
180
|
+
|
|
165
181
|
def _read_ask_response(payload: dict[str, Any]) -> dict[str, Any]:
|
|
166
182
|
"""按 question_type 读取终端回答,构造 AskResponseBody 形态。
|
|
167
183
|
|
|
@@ -182,7 +198,8 @@ def _read_ask_response(payload: dict[str, Any]) -> dict[str, Any]:
|
|
|
182
198
|
picked: list[str] = []
|
|
183
199
|
for part in indexes:
|
|
184
200
|
if part.isdigit() and 1 <= int(part) <= len(options):
|
|
185
|
-
|
|
201
|
+
# 按编号选中:回灌选项的 value 字段(与前端 AskUserCard 契约一致)
|
|
202
|
+
picked.append(_split_option(options[int(part) - 1])[0])
|
|
186
203
|
else:
|
|
187
204
|
picked.append(part)
|
|
188
205
|
if payload.get("multiple"):
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
功能概述:
|
|
19
19
|
- 执行 AI 聊天命令的主入口
|
|
20
20
|
- 辅助函数已拆分到 executor_utils.py / diff.py / display.py
|
|
21
|
+
- agent 模式经 ChatStreamRenderer(stream_renderer.py)接终端流式渲染
|
|
21
22
|
"""
|
|
22
23
|
|
|
23
24
|
from __future__ import annotations
|
|
@@ -39,6 +40,7 @@ from .executor_utils import (
|
|
|
39
40
|
)
|
|
40
41
|
from .interaction import confirm_actions as base_confirm_actions
|
|
41
42
|
from .resolver import resolve_ambiguities as base_resolve_ambiguities
|
|
43
|
+
from .stream_renderer import ChatStreamRenderer
|
|
42
44
|
|
|
43
45
|
# 为模型回复预留的 token 预算
|
|
44
46
|
RESERVED_OUTPUT_TOKENS = 8000
|
|
@@ -134,6 +136,10 @@ def execute_ai_chat(
|
|
|
134
136
|
|
|
135
137
|
agent_apply_callbacks, agent_ask_callbacks = build_agent_interaction(spinner)
|
|
136
138
|
|
|
139
|
+
# 流式渲染器:agent 模式把 runner 的 on_chunk/on_tool_* 回调接到终端,
|
|
140
|
+
# LLM 逐字输出与工具过程实时可见;legacy JSON 路径无流式管道,保持原 spinner 体验
|
|
141
|
+
stream_renderer = ChatStreamRenderer(spinner=spinner, interactive=interactive) if agent_mode else None
|
|
142
|
+
|
|
137
143
|
# 配置对话选项
|
|
138
144
|
options = ChatOptions(
|
|
139
145
|
history=history or [],
|
|
@@ -149,6 +155,7 @@ def execute_ai_chat(
|
|
|
149
155
|
apply_callbacks=agent_apply_callbacks,
|
|
150
156
|
ask_callbacks=agent_ask_callbacks,
|
|
151
157
|
dry_run_enabled=agent_apply_callbacks is not None,
|
|
158
|
+
agent_stream_callbacks=stream_renderer.callbacks() if stream_renderer else None,
|
|
152
159
|
)
|
|
153
160
|
|
|
154
161
|
try:
|
|
@@ -166,19 +173,26 @@ def execute_ai_chat(
|
|
|
166
173
|
spinner.start()
|
|
167
174
|
|
|
168
175
|
# 执行 AI 对话(CLI 是同步环境:asyncio.run 创建一次性事件循环并在结束时确保关闭,
|
|
169
|
-
# 不再使用已弃用的 get_event_loop + run_until_complete
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
+
# 不再使用已弃用的 get_event_loop + run_until_complete,避免循环泄漏)。
|
|
177
|
+
# 收尾走 finally:流式中途 Ctrl+C 等任何退出路径(含 BaseException)都先 finish
|
|
178
|
+
# 再 stop——spinner.stop 的清行序列从行首覆盖,必须保证光标已落在干净行首
|
|
179
|
+
try:
|
|
180
|
+
result = asyncio.run(
|
|
181
|
+
orchestrator.execute_chat(
|
|
182
|
+
message=message,
|
|
183
|
+
project_path=project_path,
|
|
184
|
+
context_nodes=context_nodes,
|
|
185
|
+
options=options,
|
|
186
|
+
)
|
|
176
187
|
)
|
|
177
|
-
|
|
188
|
+
finally:
|
|
189
|
+
# 收尾流式文本行(先于 spinner.stop,顺序不可颠倒)
|
|
190
|
+
if stream_renderer:
|
|
191
|
+
stream_renderer.finish()
|
|
178
192
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
193
|
+
# 停止 spinner 动画
|
|
194
|
+
if spinner:
|
|
195
|
+
spinner.stop()
|
|
182
196
|
|
|
183
197
|
# 处理结果:如果失败则返回错误
|
|
184
198
|
if not result.success:
|
|
@@ -189,11 +203,12 @@ def execute_ai_chat(
|
|
|
189
203
|
|
|
190
204
|
reply = result.reply
|
|
191
205
|
actions = result.actions
|
|
206
|
+
# 流式已把最终回复逐字上屏时不再整段重复打印(多轮中间文本已由 on_turn 重置排除)
|
|
207
|
+
reply_streamed = bool(stream_renderer and stream_renderer.final_reply_already_shown(reply))
|
|
192
208
|
|
|
193
209
|
# 在交互模式下显示 AI 的文本回复
|
|
194
|
-
if interactive:
|
|
195
|
-
|
|
196
|
-
print(reply)
|
|
210
|
+
if interactive and reply and not reply_streamed:
|
|
211
|
+
print(reply)
|
|
197
212
|
|
|
198
213
|
# 在交互模式下显示 Agent 工具轨迹(仅 Agent 模式有 tool_steps)
|
|
199
214
|
if interactive and agent_mode and result.tool_steps:
|
|
@@ -208,7 +223,8 @@ def execute_ai_chat(
|
|
|
208
223
|
_display_execution_results(result, project_path, original_files_cache)
|
|
209
224
|
|
|
210
225
|
return CommandResult.ok(
|
|
211
|
-
|
|
226
|
+
# 非交互(ai ask)单发模式由 main 打印 message:流式已输出过回复时置空避免重复
|
|
227
|
+
"" if interactive or reply_streamed else reply,
|
|
212
228
|
data={
|
|
213
229
|
"reply": reply,
|
|
214
230
|
"actions": actions,
|
|
@@ -217,10 +233,7 @@ def execute_ai_chat(
|
|
|
217
233
|
)
|
|
218
234
|
|
|
219
235
|
except Exception as e:
|
|
220
|
-
#
|
|
221
|
-
if spinner:
|
|
222
|
-
spinner.stop()
|
|
223
|
-
|
|
236
|
+
# 流式收尾与 spinner 停止已由内层 finally 覆盖(任何退出路径),此处只做错误呈现
|
|
224
237
|
logger.error(f"AI 对话失败: {e}", exc_info=True)
|
|
225
238
|
error_msg = f"AI 对话失败: {str(e)}"
|
|
226
239
|
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
# backend/app/cli/shell/commands/ai/stream_renderer.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview CLI 终端流式渲染器(AI agent 聊天)
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 把 ChatAgentRunner 的流式回调(on_chunk/on_turn/on_tool_call/on_tool_result)渲染到终端
|
|
22
|
+
- LLM 文本逐字增量打印(flush 即时),工具调用/结果打紧凑单行状态
|
|
23
|
+
- 首个内容事件到达后永久停掉 spinner——流式反馈本身即"进行中"提示
|
|
24
|
+
|
|
25
|
+
渲染协调(三个交互点):
|
|
26
|
+
- spinner: 首个 chunk / 工具事件停掉 spinner(spinner.stop 的清行序列会从行首覆盖,
|
|
27
|
+
调用前必须保证光标已落在干净行首,否则抹掉未换行的流式文本)
|
|
28
|
+
- 交互提示(两阶段确认 / ask_user): 提示在工具执行期触发,而 on_tool_call 先于工具执行
|
|
29
|
+
收尾换行,故提示出现时流式文本已完整落行,不粘连
|
|
30
|
+
- 工具轨迹: 流式期间只给轻量单行状态;循环结束后的 _display_tool_trail 总结保留,
|
|
31
|
+
两者角色不同(实时进度 vs 结束审计),不视为重复
|
|
32
|
+
|
|
33
|
+
输入示例:
|
|
34
|
+
renderer = ChatStreamRenderer(spinner=spinner, interactive=True)
|
|
35
|
+
options = ChatOptions(..., agent_stream_callbacks=renderer.callbacks())
|
|
36
|
+
|
|
37
|
+
输出示例:
|
|
38
|
+
→ 调用工具 修改配置...
|
|
39
|
+
✓ 修改配置 完成
|
|
40
|
+
已为 users.nickname 创建 chinese_mixed 字符集约束。
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
from __future__ import annotations
|
|
44
|
+
|
|
45
|
+
import sys
|
|
46
|
+
from typing import Any, TextIO
|
|
47
|
+
|
|
48
|
+
from app.cli.shell.commands.ai.executor_utils import SpinnerController
|
|
49
|
+
from app.cli.shell.formatter import Formatter
|
|
50
|
+
|
|
51
|
+
# 交互模式工具失败错误信息截断上限:单行状态不刷屏(完整 error 由结束后的
|
|
52
|
+
# _display_tool_trail 展示);非交互模式 stderr 是唯一诊断通道(工具轨迹不运行),不截断
|
|
53
|
+
_MAX_ERROR_CHARS = 60
|
|
54
|
+
|
|
55
|
+
# 工具名 → 中文标签缓存(单一事实源:ChatAgentRunner._TOOL_LABELS;
|
|
56
|
+
# 延迟导入避免 CLI 启动时把 agent 工具链全部加载进 import 图)
|
|
57
|
+
_TOOL_LABELS: dict[str, str] | None = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _tool_label(name: str) -> str:
|
|
61
|
+
"""取工具的中文展示标签,未知工具回退原始名。"""
|
|
62
|
+
global _TOOL_LABELS
|
|
63
|
+
if _TOOL_LABELS is None:
|
|
64
|
+
from app.shared.services.ai.chat_agent_runner import ChatAgentRunner
|
|
65
|
+
|
|
66
|
+
_TOOL_LABELS = dict(ChatAgentRunner._TOOL_LABELS)
|
|
67
|
+
return _TOOL_LABELS.get(name, name)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _print_to(stream: TextIO, text: str, end: str = "\n") -> None:
|
|
71
|
+
"""向流打印;GBK 等本地编码管道下遇不可编码字符(✓/✗、emoji)降级替换输出。
|
|
72
|
+
|
|
73
|
+
流式渲染发生在对话运行中途,一次编码异常会击穿整个 agent 循环——
|
|
74
|
+
真实控制台(Windows console IO)不受影响,重定向/管道场景必须兜底。
|
|
75
|
+
"""
|
|
76
|
+
try:
|
|
77
|
+
print(text, end=end, file=stream, flush=True)
|
|
78
|
+
except UnicodeEncodeError:
|
|
79
|
+
encoding = getattr(stream, "encoding", None) or "utf-8"
|
|
80
|
+
fallback = text.encode(encoding, errors="replace").decode(encoding, errors="replace")
|
|
81
|
+
print(fallback, end=end, file=stream, flush=True)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class ChatStreamRenderer:
|
|
85
|
+
"""Agent 流式回调 → 终端渲染适配器。
|
|
86
|
+
|
|
87
|
+
经 ChatOptions.agent_stream_callbacks 注入,由编排器转交给
|
|
88
|
+
ChatAgentRunner.configure_callbacks;回调在事件循环协程内同步触发,
|
|
89
|
+
仅做 print,无阻塞操作。
|
|
90
|
+
|
|
91
|
+
Attributes:
|
|
92
|
+
_spinner: 交互模式的 spinner 控制器(None 表示无动画)
|
|
93
|
+
_interactive: 是否交互模式(决定错误信息截断策略)
|
|
94
|
+
_status_stream: 过程状态(工具行/轮次分隔)输出流;非交互模式走 stderr,
|
|
95
|
+
保证 `ai ask` 管道场景 stdout 只含回复正文
|
|
96
|
+
_started: 首个内容事件已处理(spinner 已永久停止)
|
|
97
|
+
_pending_newline: 当前行有未收尾的流式文本
|
|
98
|
+
_turn_text: 最近一轮已流式打印的累积文本(判定最终回复是否已上屏)
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
spinner: SpinnerController | None = None,
|
|
104
|
+
interactive: bool = True,
|
|
105
|
+
) -> None:
|
|
106
|
+
self._spinner = spinner
|
|
107
|
+
self._interactive = interactive
|
|
108
|
+
self._status_stream = sys.stdout if interactive else sys.stderr
|
|
109
|
+
self._started = False
|
|
110
|
+
self._pending_newline = False
|
|
111
|
+
self._turn_text = ""
|
|
112
|
+
|
|
113
|
+
def callbacks(self) -> dict[str, Any]:
|
|
114
|
+
"""构造注入 ChatOptions.agent_stream_callbacks 的回调字典。"""
|
|
115
|
+
return {
|
|
116
|
+
"on_chunk": self.on_chunk,
|
|
117
|
+
"on_turn": self.on_turn,
|
|
118
|
+
"on_tool_call": self.on_tool_call,
|
|
119
|
+
"on_tool_result": self.on_tool_result,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
def _stop_spinner_once(self) -> None:
|
|
123
|
+
"""首个内容事件到达后永久停掉 spinner(此后流式事件接管反馈)。"""
|
|
124
|
+
if self._started:
|
|
125
|
+
return
|
|
126
|
+
self._started = True
|
|
127
|
+
if self._spinner:
|
|
128
|
+
self._spinner.stop()
|
|
129
|
+
|
|
130
|
+
def _terminate_line(self) -> None:
|
|
131
|
+
"""收尾未换行的流式文本行,保证后续行式输出不粘连。"""
|
|
132
|
+
if self._pending_newline:
|
|
133
|
+
_print_to(sys.stdout, "")
|
|
134
|
+
self._pending_newline = False
|
|
135
|
+
|
|
136
|
+
def on_chunk(self, text: str) -> None:
|
|
137
|
+
"""LLM 文本增量:停 spinner 后逐字打印(不主动换行,随流自然收尾)。"""
|
|
138
|
+
self._stop_spinner_once()
|
|
139
|
+
if not text:
|
|
140
|
+
return
|
|
141
|
+
self._turn_text += text
|
|
142
|
+
_print_to(sys.stdout, text, end="")
|
|
143
|
+
self._pending_newline = not text.endswith("\n")
|
|
144
|
+
|
|
145
|
+
def on_turn(self, turn: int) -> None:
|
|
146
|
+
"""轮次开始:重置本轮累积文本,仅在已有内容输出后打一个空行分隔(克制使用)。"""
|
|
147
|
+
self._turn_text = ""
|
|
148
|
+
if turn < 2 or not self._started:
|
|
149
|
+
return
|
|
150
|
+
self._terminate_line()
|
|
151
|
+
_print_to(self._status_stream, "")
|
|
152
|
+
|
|
153
|
+
def on_tool_call(self, name: str, call_id: str, turn: int) -> None:
|
|
154
|
+
"""工具调用开始:收尾流式文本行(先于停 spinner,见模块注释)后打单行状态。"""
|
|
155
|
+
self._terminate_line()
|
|
156
|
+
self._stop_spinner_once()
|
|
157
|
+
_print_to(self._status_stream, Formatter.dim(f"→ 调用工具 {_tool_label(name)}..."))
|
|
158
|
+
|
|
159
|
+
def on_tool_result(self, tr: Any) -> None:
|
|
160
|
+
"""工具调用结束:单行成败状态。
|
|
161
|
+
|
|
162
|
+
失败错误信息:交互模式截断到单行上限并补 "…" 标记(完整 error 由
|
|
163
|
+
_display_tool_trail 结束总结展示);非交互模式 stderr 是唯一诊断通道
|
|
164
|
+
(_display_tool_trail 不运行),不截断,仅把换行压成空格保持单行形状。
|
|
165
|
+
"""
|
|
166
|
+
self._terminate_line()
|
|
167
|
+
self._stop_spinner_once()
|
|
168
|
+
label = _tool_label(tr.name)
|
|
169
|
+
if tr.success:
|
|
170
|
+
_print_to(self._status_stream, Formatter.success(f"✓ {label} 完成"))
|
|
171
|
+
else:
|
|
172
|
+
error = (tr.error or "").replace("\n", " ")
|
|
173
|
+
if self._interactive and len(error) > _MAX_ERROR_CHARS:
|
|
174
|
+
error = error[:_MAX_ERROR_CHARS] + "…"
|
|
175
|
+
suffix = f" — {error}" if error else ""
|
|
176
|
+
_print_to(self._status_stream, Formatter.error(f"✗ {label} 失败{suffix}"))
|
|
177
|
+
|
|
178
|
+
def finish(self) -> None:
|
|
179
|
+
"""流结束后收尾:保证末行换行,终端留给后续输出(确认提示/轨迹/错误信息)。"""
|
|
180
|
+
self._terminate_line()
|
|
181
|
+
|
|
182
|
+
def final_reply_already_shown(self, reply: str) -> bool:
|
|
183
|
+
"""最终回复是否已随流式上屏。
|
|
184
|
+
|
|
185
|
+
runner 的 reply 即最后一轮 content,而每轮 content 均由 on_chunk 逐段
|
|
186
|
+
累积而来——两者完全相等说明全文已流式打印,调用方无需再整段重复输出。
|
|
187
|
+
"""
|
|
188
|
+
return bool(reply) and reply == self._turn_text
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
Tools:
|
|
25
25
|
- validate_data: 执行校验,返回契约 payload(docs/contracts/validate-json-v1.md)
|
|
26
26
|
- check_config: 检查项目配置加载情况(loading_errors/warnings + 装载计数)
|
|
27
|
-
- describe_constraints:
|
|
27
|
+
- describe_constraints: 列出约束类型与参数说明(refs/params 从 actions registry 派生)
|
|
28
28
|
- infer_schema: 从数据文件推断 schema 草稿
|
|
29
29
|
|
|
30
30
|
安全(P3-3):
|
|
@@ -42,24 +42,6 @@ import sys
|
|
|
42
42
|
from pathlib import Path
|
|
43
43
|
from typing import Any
|
|
44
44
|
|
|
45
|
-
# 约束类型的参数说明(类型清单从 core registry 派生,此表只补人话描述;
|
|
46
|
-
# 新增约束类型时 registry 派生会自动带上,缺描述时用通用文案兜底)
|
|
47
|
-
_CONSTRAINT_PARAM_DOCS: dict[str, dict[str, str]] = {
|
|
48
|
-
"NotNull": {"refs": "table_id + column_id", "params": "无"},
|
|
49
|
-
"Unique": {"refs": "table_id + column_ids(列表)", "params": "无"},
|
|
50
|
-
"AllowedValues": {"refs": "table_id + column_id", "params": "allowed_values: 允许值列表"},
|
|
51
|
-
"Range": {"refs": "table_id + column_id", "params": "min/max/boundary_mode(inclusive|exclusive)"},
|
|
52
|
-
"ForeignKey": {"refs": "from_table_id + from_column_id + to_table_id + to_column_id", "params": "无"},
|
|
53
|
-
"Conditional": {
|
|
54
|
-
"refs": "table_id + then_column_id + if_conditions[{if_column_id, operator, value}] + if_logic",
|
|
55
|
-
"params": "then_condition: {operator: not_null}",
|
|
56
|
-
},
|
|
57
|
-
"Scripted": {"refs": "table_id + column_id", "params": "name + expression(需 allow_eval)"},
|
|
58
|
-
"Charset": {"refs": "table_id + column_id", "params": "charset_mode: ascii|chinese|chinese_mixed"},
|
|
59
|
-
"DateLogic": {"refs": "table_id + column_id", "params": "logic_mode: compare + compare_op + reference_date"},
|
|
60
|
-
"Composite": {"refs": "table_id", "params": "logic(all|any) + sub_constraints(内嵌子约束列表)"},
|
|
61
|
-
}
|
|
62
|
-
|
|
63
45
|
|
|
64
46
|
def _allowed_roots() -> list[Path]:
|
|
65
47
|
"""路径白名单根:server 工作目录 + 环境变量追加根。"""
|
|
@@ -136,20 +118,13 @@ def tool_check_config(manifest: str) -> dict[str, Any]:
|
|
|
136
118
|
|
|
137
119
|
|
|
138
120
|
def tool_describe_constraints() -> dict[str, Any]:
|
|
139
|
-
"""
|
|
140
|
-
from app.shared.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
"type": type_name,
|
|
147
|
-
"refs": _CONSTRAINT_PARAM_DOCS.get(type_name, {}).get("refs", "见 v2-format 文档"),
|
|
148
|
-
"params": _CONSTRAINT_PARAM_DOCS.get(type_name, {}).get("params", "见 v2-format 文档"),
|
|
149
|
-
}
|
|
150
|
-
for type_name in canonical_types
|
|
151
|
-
]
|
|
152
|
-
return {"types": types, "note": "refs 指向 schema 的表/列 ID;配置格式详见插件 v2-format.md"}
|
|
121
|
+
"""列出全部约束类型与参数说明(refs/params 均从 actions registry 单一事实源派生)。"""
|
|
122
|
+
from app.shared.services.llm.actions.registry import build_constraint_param_docs_structured
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
"types": build_constraint_param_docs_structured(),
|
|
126
|
+
"note": "refs 指向 schema 的表/列 ID;params 为 V2 文件 params 区的 snake_case 键(含值域/默认值);配置格式详见插件 v2-format.md",
|
|
127
|
+
}
|
|
153
128
|
|
|
154
129
|
|
|
155
130
|
def tool_infer_schema(
|
|
@@ -278,7 +253,9 @@ def _warm_up_imports() -> None:
|
|
|
278
253
|
不变量:工具线程内出现的所有 app 内延迟导入都必须在此登记——2026-09-21
|
|
279
254
|
审计实证 json_payload(tool_validate_data 内)与 project_loader
|
|
280
255
|
(executor.build_dataset_schema 惰性导入)破坏该不变量,已补齐。
|
|
256
|
+
describe_constraints 的 actions registry 延迟导入(轻量纯数据模块)同样登记。
|
|
281
257
|
"""
|
|
258
|
+
from app.shared.services.llm.actions import registry # noqa: F401
|
|
282
259
|
from app.shared.services.project_loader import build_dataset_schema # noqa: F401
|
|
283
260
|
from app.shared.services.schema_inference import infer_schema # noqa: F401
|
|
284
261
|
from app.shared.services.validation import (
|