"""FastAPI route registration for resume editing and optimization.""" from typing import Any, Callable from fastapi import FastAPI, Header from fastapi.responses import StreamingResponse from .agent import ResumeAgent from .fsm import FSMError from .models import ActionResponse, OptimizeEntryRequest, OptimizeRequest, ResumePatchRequest from .optimization_models import ( OptimizationRunView, OptimizationStartRequest, TargetPositionRequest, ) from .resume_api_models import SkillRecommendationRequest, SkillRecommendationResponse def register_resume_routes( application: FastAPI, agent: ResumeAgent, prefix: str, *, authorize_session_request: Callable[[str, str | None], str | None] | None = None, ) -> None: authorize_session_request = authorize_session_request or ( lambda _session_id, _authorization: None ) def mutate_with_sync( session_id: str, external_token: str | None, operation: Callable[[], Any], *, allow_pulled_resume: bool = False, ) -> Any: if external_token is None: return operation() return agent.mutate_with_offerpai_sync( session_id, external_token, operation, allow_pulled_resume=allow_pulled_resume, ) @application.patch( f"{prefix}/sessions/{{session_id}}/resume", response_model=ActionResponse, tags=["resume-agent"], ) def patch_resume( session_id: str, request: ResumePatchRequest, authorization: str | None = Header(default=None), ) -> ActionResponse: external_token = authorize_session_request(session_id, authorization) return mutate_with_sync( session_id, external_token, lambda: agent.patch_resume(session_id, request), allow_pulled_resume=True, ) @application.post( f"{prefix}/sessions/{{session_id}}/resume/profile-summary/generate", response_model=ActionResponse, tags=["resume-agent"], ) def generate_profile_summary( session_id: str, authorization: str | None = Header(default=None), ) -> ActionResponse: external_token = authorize_session_request(session_id, authorization) return mutate_with_sync( session_id, external_token, lambda: agent.generate_profile_summary(session_id), ) @application.post( f"{prefix}/sessions/{{session_id}}/resume/profile-summary/confirm", response_model=ActionResponse, tags=["resume-agent"], ) def confirm_profile_summary( session_id: str, authorization: str | None = Header(default=None), ) -> ActionResponse: external_token = authorize_session_request(session_id, authorization) return mutate_with_sync( session_id, external_token, lambda: agent.confirm_profile_summary(session_id), ) @application.post( f"{prefix}/sessions/{{session_id}}/resume/profile-summary/reject", response_model=ActionResponse, tags=["resume-agent"], ) def reject_profile_summary( session_id: str, authorization: str | None = Header(default=None), ) -> ActionResponse: external_token = authorize_session_request(session_id, authorization) return mutate_with_sync( session_id, external_token, lambda: agent.reject_profile_summary(session_id), ) @application.post( f"{prefix}/sessions/{{session_id}}/resume/skills/recommend", response_model=SkillRecommendationResponse, tags=["resume-agent"], ) def recommend_skills( session_id: str, request: SkillRecommendationRequest, authorization: str | None = Header(default=None), ) -> SkillRecommendationResponse: external_token = authorize_session_request(session_id, authorization) if external_token is not None: agent.reconcile_resume_with_offerpai(session_id, external_token) return SkillRecommendationResponse( candidates=agent.recommend_skills(session_id, request.question) ) @application.post( f"{prefix}/sessions/{{session_id}}/resume/optimize", response_model=ActionResponse, tags=["resume-agent"], ) def optimize_entry( session_id: str, request: OptimizeRequest, authorization: str | None = Header(default=None), ) -> ActionResponse: external_token = authorize_session_request(session_id, authorization) return mutate_with_sync( session_id, external_token, lambda: agent.optimize_entry(session_id, request), ) @application.post( f"{prefix}/sessions/{{session_id}}/resume/optimize/confirm", response_model=ActionResponse, tags=["resume-agent"], ) def confirm_optimize( session_id: str, request: OptimizeEntryRequest, authorization: str | None = Header(default=None), ) -> ActionResponse: external_token = authorize_session_request(session_id, authorization) return mutate_with_sync( session_id, external_token, lambda: agent.confirm_optimize(session_id, request), ) @application.post( f"{prefix}/sessions/{{session_id}}/resume/optimize/reject", response_model=ActionResponse, tags=["resume-agent"], ) def reject_optimize( session_id: str, request: OptimizeEntryRequest, authorization: str | None = Header(default=None), ) -> ActionResponse: external_token = authorize_session_request(session_id, authorization) return mutate_with_sync( session_id, external_token, lambda: agent.reject_optimize(session_id, request), ) @application.post( f"{prefix}/sessions/{{session_id}}/resume/optimize/undo", response_model=ActionResponse, tags=["resume-agent"], ) def undo_optimize( session_id: str, request: OptimizeEntryRequest, authorization: str | None = Header(default=None), ) -> ActionResponse: external_token = authorize_session_request(session_id, authorization) return mutate_with_sync( session_id, external_token, lambda: agent.undo_optimize(session_id, request), ) @application.post( f"{prefix}/sessions/{{session_id}}/target-position", tags=["resume-agent"], ) def set_session_target_position( session_id: str, request: TargetPositionRequest, authorization: str | None = Header(default=None), ) -> dict[str, object]: external_token = authorize_session_request(session_id, authorization) return mutate_with_sync( session_id, external_token, lambda: agent.set_target_position(session_id, request.target_position), ) @application.post( f"{prefix}/sessions/{{session_id}}/resume/optimize/light", response_model=OptimizationRunView, tags=["resume-agent"], ) def optimize_light( session_id: str, request: OptimizationStartRequest, authorization: str | None = Header(default=None), ) -> OptimizationRunView: external_token = authorize_session_request(session_id, authorization) def mutate() -> OptimizationRunView: limiter = getattr(application.state, "light_opt_limiter", None) if limiter is not None and not limiter.allow(session_id): raise FSMError( "rate_limited", "操作过于频繁,请稍后再试(轻度优化每小时最多 20 次)。", status_code=429, ) return agent.optimize_light(session_id, request) return mutate_with_sync( session_id, external_token, mutate, ) @application.get( f"{prefix}/sessions/{{session_id}}/resume/optimize/runs/active", response_model=list[OptimizationRunView], tags=["resume-agent"], ) def list_active_optimization_runs( session_id: str, authorization: str | None = Header(default=None), ) -> list[OptimizationRunView]: external_token = authorize_session_request(session_id, authorization) if external_token is not None: agent.reconcile_resume_with_offerpai(session_id, external_token) return agent.list_active_optimization_runs(session_id) @application.post( f"{prefix}/sessions/{{session_id}}/resume/optimize/runs/{{run_id}}/confirm", response_model=OptimizationRunView, tags=["resume-agent"], ) def confirm_deep_optimization( session_id: str, run_id: str, authorization: str | None = Header(default=None), ) -> OptimizationRunView: external_token = authorize_session_request(session_id, authorization) return mutate_with_sync( session_id, external_token, lambda: agent.confirm_deep_optimization(session_id, run_id), ) @application.post( f"{prefix}/sessions/{{session_id}}/resume/optimize/runs/{{run_id}}/reject", response_model=OptimizationRunView, tags=["resume-agent"], ) def reject_optimization( session_id: str, run_id: str, authorization: str | None = Header(default=None), ) -> OptimizationRunView: external_token = authorize_session_request(session_id, authorization) return mutate_with_sync( session_id, external_token, lambda: agent.reject_optimization(session_id, run_id), )