@@ -43,6 +43,20 @@ class AgentCreate(BaseModel):
4343 description : str
4444 tools : list [str ]
4545
46+
47+ class ToolUpdate (BaseModel ):
48+ description : Optional [str ] = None
49+ side_effect_level : Optional [str ] = None
50+ data_classes : Optional [list [str ]] = None
51+ tags : Optional [list [str ]] = None
52+ redaction_paths : Optional [list [str ]] = None
53+ allowed_roles : Optional [list [str ]] = None
54+
55+
56+ class AgentUpdate (BaseModel ):
57+ description : Optional [str ] = None
58+ tools : Optional [list [str ]] = None
59+
4660@asynccontextmanager
4761async def lifespan (app : FastAPI ):
4862 register_example_tools ()
@@ -152,6 +166,90 @@ def create_agent(
152166 return {"status" : "ok" , "agent_id" : agent .agent_id }
153167
154168
169+ @app .patch ("/registry/tools/{tool_id}" )
170+ def update_tool (
171+ tool_id : str ,
172+ payload : ToolUpdate ,
173+ admin_token : Optional [str ] = Header (None , alias = "Authorization" ),
174+ roles_header : Optional [str ] = Header (None , alias = "X-OCPA-Roles" ),
175+ ) -> Dict [str , Any ]:
176+ _require_admin (admin_token , roles_header )
177+ existing = registry .get_tool (tool_id )
178+ if not existing :
179+ raise HTTPException (status_code = 404 , detail = "tool not found" )
180+ updates = {k : v for k , v in payload .model_dump (exclude_none = True ).items ()}
181+ updated = existing .__dict__ | updates
182+ if updated .get ("side_effect_level" ) not in {"none" , "low" , "high" }:
183+ raise HTTPException (status_code = 400 , detail = "invalid side_effect_level" )
184+ _require_registry_policy ("update" , "tool" , {"tool_id" : tool_id , ** updates }, roles_header )
185+ registry .register_tool (ToolMetadata (** updated ))
186+ emit_validated (
187+ lambda : build_registry_audit_event (
188+ action = "update" ,
189+ resource_type = "tool" ,
190+ resource_id = tool_id ,
191+ actor = _actor_from_headers (roles_header ),
192+ env = os .environ .get ("OCPA_ENV" , "dev" ),
193+ request = updates ,
194+ )
195+ )
196+ return {"status" : "ok" , "tool_id" : tool_id }
197+
198+
199+ @app .delete ("/registry/tools/{tool_id}" )
200+ def delete_tool (
201+ tool_id : str ,
202+ admin_token : Optional [str ] = Header (None , alias = "Authorization" ),
203+ roles_header : Optional [str ] = Header (None , alias = "X-OCPA-Roles" ),
204+ ) -> Dict [str , Any ]:
205+ _require_admin (admin_token , roles_header )
206+ _require_registry_policy ("delete" , "tool" , {"tool_id" : tool_id }, roles_header )
207+ if not registry .get_tool (tool_id ):
208+ raise HTTPException (status_code = 404 , detail = "tool not found" )
209+ # remove tool and any references in agents
210+ registry ._tools .pop (tool_id , None ) # noqa: SLF001
211+ for agent in registry ._agents .values (): # noqa: SLF001
212+ if tool_id in agent .tools :
213+ agent .tools = [t for t in agent .tools if t != tool_id ]
214+ registry ._save ()
215+ emit_validated (
216+ lambda : build_registry_audit_event (
217+ action = "delete" ,
218+ resource_type = "tool" ,
219+ resource_id = tool_id ,
220+ actor = _actor_from_headers (roles_header ),
221+ env = os .environ .get ("OCPA_ENV" , "dev" ),
222+ request = {"tool_id" : tool_id },
223+ )
224+ )
225+ return {"status" : "deleted" , "tool_id" : tool_id }
226+
227+
228+ @app .delete ("/registry/agents/{agent_id}" )
229+ def delete_agent (
230+ agent_id : str ,
231+ admin_token : Optional [str ] = Header (None , alias = "Authorization" ),
232+ roles_header : Optional [str ] = Header (None , alias = "X-OCPA-Roles" ),
233+ ) -> Dict [str , Any ]:
234+ _require_admin (admin_token , roles_header )
235+ _require_registry_policy ("delete" , "agent" , {"agent_id" : agent_id }, roles_header )
236+ if not registry .get_agent (agent_id ):
237+ raise HTTPException (status_code = 404 , detail = "agent not found" )
238+ registry ._agents .pop (agent_id , None ) # noqa: SLF001
239+ registry ._save ()
240+ emit_validated (
241+ lambda : build_registry_audit_event (
242+ action = "delete" ,
243+ resource_type = "agent" ,
244+ resource_id = agent_id ,
245+ actor = _actor_from_headers (roles_header ),
246+ env = os .environ .get ("OCPA_ENV" , "dev" ),
247+ request = {"agent_id" : agent_id },
248+ )
249+ )
250+ return {"status" : "deleted" , "agent_id" : agent_id }
251+
252+
155253def _require_admin (auth_header : Optional [str ], roles_header : Optional [str ]) -> None :
156254 required_role = os .environ .get ("OCPA_ADMIN_ROLE" , "admin" )
157255 verify_bearer_token (auth_header , required_role , role_claim = os .environ .get ("OCPA_ROLE_CLAIM" , "roles" ))
0 commit comments