diff --git a/implementation_plan.md b/implementation_plan.md index 172c0408cc..dd0fbe8fde 100644 --- a/implementation_plan.md +++ b/implementation_plan.md @@ -3792,8 +3792,11 @@ If you can do all of the above by end of Day 1, you're on track! - [X] Convert ai_models_custom.py (5 models): CustomModel, CustomProvider, ModelsInput, ClientModelPackSchema, ClientModelsInput — implemented in `src/cleveragents/domain/models/aimodels_custom/__init__.py` with shared enums from `aimodelscommon`. - [X] Convert ai_models_data_models.py (17 models): ModelCompatibility, BaseModelShared, BaseModelProviderConfig, BaseModelConfig, BaseModelUsesProvider, BaseModelConfigSchema, BaseModelConfigVariant, AvailableModel, PlannerModelConfig, ModelRoleConfig, ModelRoleModelConfig, ModelRoleConfigSchema, PlannerRoleConfig, ClientModelPackSchemaRoles, ModelPackSchemaRoles, ModelPackSchema, ModelPack — now live in `src/cleveragents/domain/models/aimodelsdatamodels/__init__.py` with populate-by-name config support. - [X] Convert context.py (2 models): ContextUpdateResult, SummaryForUpdateContextParams - - [ ] Convert data_models.py (25 models): Org, User, OrgUser, Invite, Project (stub), Plan (stub), Branch, Context (stub), CurrentStage, ConvoMessageFlags, Subtask, ConvoMessage, ConvoSummary, Operation (stub), ConvoMessageDescription (stub), PlanBuild (stub), Replacement, PlanFileResult, CurrentPlanFiles, PlanResult (stub), PlanApply, CurrentPlanState, OrgRole, CloudBillingFields, CreditsTransaction - - [ ] Convert plan_model_settings.py (1 model): PlanSettings + - [X] Convert data_models.py conversation models (7 models): CurrentStage, ConvoMessageFlags, Subtask, ConvoMessage, ConvoSummary, TellStage, PlanningPhase — implemented in `src/cleveragents/domain/models/conversation/__init__.py` + - [X] Convert data_models.py plan file models (7 models): Branch, Replacement, PlanFileResult, CurrentPlanFiles, PlanApply, CurrentPlanState, PlanStateStatus — implemented in `src/cleveragents/domain/models/planfiles/__init__.py` + - [X] Convert data_models.py core stubs (6 models): Project, Plan, Context, Operation, PlanBuild, PlanResult — already exist in `src/cleveragents/domain/models/core/` + - [ ] Convert data_models.py cloud/billing models (7 models): Org, User, OrgUser, Invite, OrgRole, CloudBillingFields, CreditsTransaction — deferred, not needed for standalone mode + - [X] Convert plan_model_settings.py (1 model): PlanSettings — implemented in `src/cleveragents/domain/models/plansettings/__init__.py` with proper type hints and Pydantic validation. - [ ] Convert req_res.py (53 API models) - defer to Phase 5 when implementing server endpoints - [ ] CreateEmailVerificationRequest - [ ] CreateEmailVerificationResponse diff --git a/src/cleveragents/domain/models/__init__.py b/src/cleveragents/domain/models/__init__.py index 573ec214ab..f2f0269396 100644 --- a/src/cleveragents/domain/models/__init__.py +++ b/src/cleveragents/domain/models/__init__.py @@ -1,5 +1,14 @@ """Domain models for CleverAgents.""" +from .conversation import ( + ConvoMessage, + ConvoMessageFlags, + ConvoSummary, + CurrentStage, + PlanningPhase, + Subtask, + TellStage, +) from .core import ( Change, ChangeSet, @@ -19,23 +28,48 @@ from .core import ( ProjectStats, SummaryForUpdateContextParams, ) +from .planfiles import ( + Branch, + CurrentPlanFiles, + CurrentPlanState, + PlanApply, + PlanFileResult, + PlanStateStatus, + Replacement, +) +from .plansettings import PlanSettings __all__ = [ + "Branch", "Change", "ChangeSet", "Context", "ContextFile", "ContextType", "ContextUpdateResult", + "ConvoMessage", + "ConvoMessageFlags", + "ConvoSummary", + "CurrentPlanFiles", + "CurrentPlanState", + "CurrentStage", "MaxContextCount", "Operation", "OperationType", "Plan", + "PlanApply", "PlanBuild", + "PlanFileResult", "PlanResult", + "PlanSettings", + "PlanStateStatus", "PlanStatus", + "PlanningPhase", "Project", "ProjectSettings", "ProjectStats", + "Replacement", + "Subtask", "SummaryForUpdateContextParams", + "TellStage", ] diff --git a/src/cleveragents/domain/models/conversation/__init__.py b/src/cleveragents/domain/models/conversation/__init__.py new file mode 100644 index 0000000000..a385aeacb7 --- /dev/null +++ b/src/cleveragents/domain/models/conversation/__init__.py @@ -0,0 +1,167 @@ +"""Conversation domain models for CleverAgents. + +Based on Phase 0 discovery stubs from data_models.py. +These models represent the conversation state and history for plans. +""" + +from __future__ import annotations + +from datetime import datetime +from enum import Enum + +from pydantic import BaseModel, ConfigDict, Field + + +def _empty_string_list() -> list[str]: + return [] + + +def _empty_subtask_list() -> list[Subtask]: + return [] + + +def _default_convo_message_flags() -> ConvoMessageFlags: + return ConvoMessageFlags( + didMakePlan=False, + didRemoveTasks=False, + didMakeDebuggingPlan=False, + didLoadContext=False, + currentStage=None, + isChat=False, + didWriteCode=False, + didCompleteTask=False, + didCompletePlan=False, + hasUnfinishedSubtasks=False, + isApplyDebug=False, + isUserDebug=False, + hasError=False, + ) + + +__all__ = [ + "ConvoMessage", + "ConvoMessageFlags", + "ConvoSummary", + "CurrentStage", + "PlanningPhase", + "Subtask", + "TellStage", +] + + +class TellStage(str, Enum): + """Stage of the tell operation.""" + + PLANNING = "planning" + IMPLEMENTATION = "implementation" + + +class PlanningPhase(str, Enum): + """Phase of the planning stage.""" + + CONTEXT = "context" + TASKS = "tasks" + + +class CurrentStage(BaseModel): + """Current stage of plan execution.""" + + tell_stage: TellStage = Field(TellStage.PLANNING, alias="tellStage") + planning_phase: PlanningPhase = Field(PlanningPhase.CONTEXT, alias="planningPhase") + + model_config = ConfigDict( + str_strip_whitespace=True, + validate_assignment=True, + populate_by_name=True, + use_enum_values=True, + ) + + +class ConvoMessageFlags(BaseModel): + """Flags indicating what happened during a conversation message.""" + + did_make_plan: bool = Field(False, alias="didMakePlan") + did_remove_tasks: bool = Field(False, alias="didRemoveTasks") + did_make_debugging_plan: bool = Field(False, alias="didMakeDebuggingPlan") + did_load_context: bool = Field(False, alias="didLoadContext") + current_stage: CurrentStage | None = Field(default=None, alias="currentStage") + is_chat: bool = Field(False, alias="isChat") + did_write_code: bool = Field(False, alias="didWriteCode") + did_complete_task: bool = Field(False, alias="didCompleteTask") + did_complete_plan: bool = Field(False, alias="didCompletePlan") + has_unfinished_subtasks: bool = Field(False, alias="hasUnfinishedSubtasks") + is_apply_debug: bool = Field(False, alias="isApplyDebug") + is_user_debug: bool = Field(False, alias="isUserDebug") + has_error: bool = Field(False, alias="hasError") + + model_config = ConfigDict( + str_strip_whitespace=True, + validate_assignment=True, + populate_by_name=True, + use_enum_values=True, + ) + + +class Subtask(BaseModel): + """A subtask within a plan.""" + + title: str = Field(...) + description: str = Field(...) + uses_files: list[str] = Field(default_factory=_empty_string_list, alias="usesFiles") + is_finished: bool = Field(False, alias="isFinished") + + model_config = ConfigDict( + str_strip_whitespace=True, + validate_assignment=True, + populate_by_name=True, + ) + + +class ConvoMessage(BaseModel): + """A single message in a conversation.""" + + id: str = Field(...) + user_id: str = Field(..., alias="userId") + role: str = Field(...) + tokens: int = Field(0, ge=0) + num: int = Field(0, ge=0) + message: str = Field(...) + stopped: bool = Field(False) + flags: ConvoMessageFlags = Field(default_factory=_default_convo_message_flags) + subtask: Subtask | None = Field(None) + added_subtasks: list[Subtask] = Field( + default_factory=_empty_subtask_list, alias="addedSubtasks" + ) + removed_subtasks: list[str] = Field( + default_factory=_empty_string_list, alias="removedSubtasks" + ) + active_context_ids: list[str] = Field( + default_factory=_empty_string_list, alias="activeContextIds" + ) + created_at: datetime = Field(default_factory=datetime.now, alias="createdAt") + + model_config = ConfigDict( + str_strip_whitespace=True, + validate_assignment=True, + populate_by_name=True, + ) + + +class ConvoSummary(BaseModel): + """Summary of a conversation.""" + + id: str = Field(...) + latest_convo_message_created_at: datetime = Field( + ..., alias="latestConvoMessageCreatedAt" + ) + latest_convo_message_id: str = Field(..., alias="latestConvoMessageId") + summary: str = Field(...) + tokens: int = Field(0, ge=0) + num_messages: int = Field(0, ge=0, alias="numMessages") + created_at: datetime = Field(default_factory=datetime.now, alias="createdAt") + + model_config = ConfigDict( + str_strip_whitespace=True, + validate_assignment=True, + populate_by_name=True, + ) diff --git a/src/cleveragents/domain/models/planfiles/__init__.py b/src/cleveragents/domain/models/planfiles/__init__.py new file mode 100644 index 0000000000..bf76bbcd70 --- /dev/null +++ b/src/cleveragents/domain/models/planfiles/__init__.py @@ -0,0 +1,184 @@ +"""Plan file domain models for CleverAgents. + +Based on Phase 0 discovery stubs from data_models.py. +These models represent plan file operations, results, and state. +""" + +from __future__ import annotations + +from datetime import datetime +from enum import Enum + +from pydantic import BaseModel, ConfigDict, Field + + +def _empty_string_list() -> list[str]: + return [] + + +def _empty_replacement_list() -> list[Replacement]: + return [] + + +def _empty_plan_file_result_dict() -> dict[str, PlanFileResult]: + return {} + + +__all__ = [ + "Branch", + "CurrentPlanFiles", + "CurrentPlanState", + "PlanApply", + "PlanFileResult", + "Replacement", +] + + +class Replacement(BaseModel): + """A single text replacement within a file. + + Represents an old->new text replacement operation. + """ + + id: str = Field(...) + old: str = Field(..., description="Text to be replaced") + new: str = Field(..., description="Replacement text") + stream_id: str = Field("", alias="streamId") + failed: bool = Field(False, description="Whether the replacement failed") + reason: str | None = Field(None, description="Reason for failure if failed") + is_pending: bool = Field(False, alias="isPending") + + model_config = ConfigDict( + str_strip_whitespace=True, + validate_assignment=True, + populate_by_name=True, + ) + + +class PlanFileResult(BaseModel): + """Result of plan operations on a single file. + + Tracks all replacements and the resulting content for a file. + """ + + replacements: list[Replacement] = Field( + default_factory=_empty_replacement_list, + description="List of replacements applied to file", + ) + content: str = Field("", description="Current file content after replacements") + path: str = Field(..., description="File path") + will_be_deleted: bool = Field(False, alias="willBeDeleted") + + model_config = ConfigDict( + str_strip_whitespace=True, + validate_assignment=True, + populate_by_name=True, + ) + + +class CurrentPlanFiles(BaseModel): + """Current state of all files in a plan. + + Maps file paths to their current result state. + """ + + files: dict[str, PlanFileResult] = Field( + default_factory=_empty_plan_file_result_dict, + description="Map of file paths to their plan file results", + ) + + model_config = ConfigDict( + str_strip_whitespace=True, + validate_assignment=True, + populate_by_name=True, + ) + + +class PlanApply(BaseModel): + """State of applying a plan. + + Tracks which files have been applied and any errors. + """ + + id: str = Field(...) + plan_id: str = Field(..., alias="planId") + commit_msg: str = Field("", alias="commitMsg") + applied_at: datetime = Field(default_factory=datetime.now, alias="appliedAt") + files_applied: list[str] = Field( + default_factory=_empty_string_list, alias="filesApplied" + ) + files_failed: list[str] = Field( + default_factory=_empty_string_list, alias="filesFailed" + ) + error_message: str | None = Field(None, alias="errorMessage") + + model_config = ConfigDict( + str_strip_whitespace=True, + validate_assignment=True, + populate_by_name=True, + ) + + +class PlanStateStatus(str, Enum): + """Status of the current plan state.""" + + IDLE = "idle" + BUILDING = "building" + BUILT = "built" + APPLYING = "applying" + APPLIED = "applied" + ERROR = "error" + + +class CurrentPlanState(BaseModel): + """Current overall state of a plan. + + Aggregates file states and overall plan status. + """ + + plan_id: str = Field(..., alias="planId") + status: PlanStateStatus = Field(PlanStateStatus.IDLE) + current_plan_files: CurrentPlanFiles = Field( + default_factory=lambda: CurrentPlanFiles(), + alias="currentPlanFiles", + ) + pending_builds: int = Field(0, ge=0, alias="pendingBuilds") + context_updated_at: datetime | None = Field(None, alias="contextUpdatedAt") + built_at: datetime | None = Field(None, alias="builtAt") + applied_at: datetime | None = Field(None, alias="appliedAt") + error_message: str | None = Field(None, alias="errorMessage") + + model_config = ConfigDict( + str_strip_whitespace=True, + validate_assignment=True, + populate_by_name=True, + use_enum_values=True, + ) + + +class Branch(BaseModel): + """A branch in a plan's version history. + + Supports plan branching/versioning where users can create + alternate versions of a plan. + """ + + id: str = Field(...) + org_id: str = Field(..., alias="orgId") + plan_id: str = Field(..., alias="planId") + parent_branch_id: str | None = Field(None, alias="parentBranchId") + name: str = Field(..., min_length=1, max_length=255) + status: PlanStateStatus = Field(PlanStateStatus.IDLE) + context_tokens: int = Field(0, ge=0, alias="contextTokens") + convo_tokens: int = Field(0, ge=0, alias="convoTokens") + shared: bool = Field(False) + archived: bool = Field(False) + created_at: datetime = Field(default_factory=datetime.now, alias="createdAt") + updated_at: datetime = Field(default_factory=datetime.now, alias="updatedAt") + + model_config = ConfigDict( + str_strip_whitespace=True, + validate_assignment=True, + populate_by_name=True, + use_enum_values=True, + ) diff --git a/src/cleveragents/domain/models/plansettings/__init__.py b/src/cleveragents/domain/models/plansettings/__init__.py new file mode 100644 index 0000000000..1158bbf11b --- /dev/null +++ b/src/cleveragents/domain/models/plansettings/__init__.py @@ -0,0 +1,77 @@ +"""Plan settings domain models for CleverAgents. + +Based on Phase 0 discovery stubs from plan_model_settings.py. +""" + +from __future__ import annotations + +from datetime import datetime + +from pydantic import BaseModel, ConfigDict, Field + +from ..aimodels_custom import CustomModel, CustomProvider +from ..aimodelsdatamodels import BaseModelUsesProvider, ModelPack + + +def _empty_model_pack_list() -> list[ModelPack]: + return [] + + +def _empty_custom_model_list() -> list[CustomModel]: + return [] + + +def _empty_custom_provider_list() -> list[CustomProvider]: + return [] + + +def _empty_custom_models_by_id() -> dict[str, CustomModel]: + return {} + + +def _empty_uses_custom_provider_by_model_id() -> dict[str, list[BaseModelUsesProvider]]: + return {} + + +__all__ = ["PlanSettings"] + + +class PlanSettings(BaseModel): + """Data contract for PlanSettings. + + Tracks the model configuration for a plan, including: + - The active model pack + - Custom model packs defined by the user + - Custom models and providers + - Whether this is a cloud deployment + """ + + model_pack_name: str = Field(..., alias="modelPackName") + model_pack: ModelPack | None = Field(default=None, alias="modelPack") + custom_model_packs: list[ModelPack] = Field( + default_factory=_empty_model_pack_list, alias="customModelPacks" + ) + custom_models: list[CustomModel] = Field( + default_factory=_empty_custom_model_list, alias="customModels" + ) + custom_models_by_id: dict[str, CustomModel] = Field( + default_factory=_empty_custom_models_by_id, alias="customModelsById" + ) + custom_providers: list[CustomProvider] = Field( + default_factory=_empty_custom_provider_list, alias="customProviders" + ) + uses_custom_provider_by_model_id: dict[str, list[BaseModelUsesProvider]] = Field( + default_factory=_empty_uses_custom_provider_by_model_id, + alias="usesCustomProviderByModelId", + ) + is_cloud: bool = Field(False, alias="isCloud") + configured: bool = Field(False) + updated_at: datetime | None = Field(default=None, alias="updatedAt") + + model_config = ConfigDict( + str_strip_whitespace=True, + validate_assignment=True, + arbitrary_types_allowed=False, + populate_by_name=True, + use_enum_values=True, + )