fix(agent): enforce idempotent resume transition
This commit is contained in:
@@ -2,10 +2,11 @@ from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from decimal import Decimal
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import HTTPException
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
@@ -29,6 +30,10 @@ if TYPE_CHECKING:
|
||||
logger = get_logger("v1.agent.service")
|
||||
|
||||
|
||||
class ResumeDecisionResult(BaseModel):
|
||||
applied: bool
|
||||
|
||||
|
||||
def build_session_title(first_message: str, *, now: datetime) -> str:
|
||||
title = first_message.strip().replace("\n", " ")[:24]
|
||||
if not title:
|
||||
@@ -335,3 +340,38 @@ class AgentChatService(BaseService):
|
||||
raise ValueError("Interrupt ID mismatch")
|
||||
snapshot["pending_tool_call"]["status"] = status
|
||||
session.state_snapshot = snapshot
|
||||
|
||||
async def apply_resume_decision(
|
||||
self,
|
||||
*,
|
||||
session_id: UUID,
|
||||
interrupt_id: str,
|
||||
decision: dict[str, Any],
|
||||
) -> ResumeDecisionResult:
|
||||
stmt = select(AgentChatSession).where(AgentChatSession.id == session_id)
|
||||
session = await self._session.scalar(stmt)
|
||||
if session is None:
|
||||
raise ValueError(f"Session {session_id} not found")
|
||||
|
||||
snapshot = session.state_snapshot
|
||||
if snapshot is None or "pending_tool_call" not in snapshot:
|
||||
return ResumeDecisionResult(applied=False)
|
||||
|
||||
pending = snapshot["pending_tool_call"]
|
||||
if pending["interrupt_id"] != interrupt_id:
|
||||
return ResumeDecisionResult(applied=False)
|
||||
|
||||
if pending["status"] != "PENDING_APPROVAL":
|
||||
return ResumeDecisionResult(applied=False)
|
||||
|
||||
decision_value = decision.get("decision", "approved")
|
||||
if decision_value == "approved":
|
||||
new_status = "APPROVED_EXECUTING"
|
||||
else:
|
||||
new_status = "REJECTED"
|
||||
|
||||
snapshot["pending_tool_call"]["status"] = new_status
|
||||
snapshot["pending_tool_call"]["decision"] = decision
|
||||
session.state_snapshot = snapshot
|
||||
|
||||
return ResumeDecisionResult(applied=True)
|
||||
|
||||
Reference in New Issue
Block a user