1919 ConversationResponse ,
2020 ConversationsListResponse ,
2121 UnauthorizedResponse ,
22+ NotFoundResponse ,
23+ AccessDeniedResponse ,
24+ BadRequestResponse ,
25+ ServiceUnavailableResponse ,
2226)
2327from utils .endpoints import (
2428 check_configuration_loaded ,
2529 delete_conversation ,
26- validate_conversation_ownership ,
30+ can_access_conversation ,
31+ retrieve_conversation ,
2732)
2833from utils .suid import check_suid
2934
3237
3338conversation_responses : dict [int | str , dict [str , Any ]] = {
3439 200 : {
35- "conversation_id" : "123e4567-e89b-12d3-a456-426614174000" ,
36- "chat_history" : [
37- {
38- "messages" : [
39- {"content" : "Hi" , "type" : "user" },
40- {"content" : "Hello!" , "type" : "assistant" },
41- ],
42- "started_at" : "2024-01-01T00:00:00Z" ,
43- "completed_at" : "2024-01-01T00:00:05Z" ,
44- }
45- ],
40+ "model" : ConversationResponse ,
41+ "description" : "Conversation retrieved successfully" ,
4642 },
4743 400 : {
48- "description " : "Missing or invalid credentials provided by client" ,
49- "model " : UnauthorizedResponse ,
44+ "model " : BadRequestResponse ,
45+ "description " : "Invalid request" ,
5046 },
5147 401 : {
52- "description" : "Unauthorized: Invalid or missing Bearer token" ,
5348 "model" : UnauthorizedResponse ,
49+ "description" : "Unauthorized: Invalid or missing Bearer token" ,
50+ },
51+ 403 : {
52+ "model" : AccessDeniedResponse ,
53+ "description" : "Client does not have permission to access conversation" ,
5454 },
5555 404 : {
56- "detail" : {
57- "response" : "Conversation not found" ,
58- "cause" : "The specified conversation ID does not exist." ,
59- }
56+ "model" : NotFoundResponse ,
57+ "description" : "Conversation not found" ,
6058 },
6159 503 : {
62- "detail" : {
63- "response" : "Unable to connect to Llama Stack" ,
64- "cause" : "Connection error." ,
65- }
60+ "model" : ServiceUnavailableResponse ,
61+ "description" : "Service unavailable" ,
6662 },
6763}
6864
6965conversation_delete_responses : dict [int | str , dict [str , Any ]] = {
7066 200 : {
71- "conversation_id" : "123e4567-e89b-12d3-a456-426614174000" ,
72- "success" : True ,
73- "message" : "Conversation deleted successfully" ,
67+ "model" : ConversationDeleteResponse ,
68+ "description" : "Conversation deleted successfully" ,
7469 },
7570 400 : {
76- "description " : "Missing or invalid credentials provided by client" ,
77- "model " : UnauthorizedResponse ,
71+ "model " : BadRequestResponse ,
72+ "description " : "Invalid request" ,
7873 },
7974 401 : {
80- "description" : "Unauthorized: Invalid or missing Bearer token" ,
8175 "model" : UnauthorizedResponse ,
76+ "description" : "Unauthorized: Invalid or missing Bearer token" ,
77+ },
78+ 403 : {
79+ "model" : AccessDeniedResponse ,
80+ "description" : "Client does not have permission to access conversation" ,
8281 },
8382 404 : {
84- "detail" : {
85- "response" : "Conversation not found" ,
86- "cause" : "The specified conversation ID does not exist." ,
87- }
83+ "model" : NotFoundResponse ,
84+ "description" : "Conversation not found" ,
8885 },
8986 503 : {
90- "detail" : {
91- "response" : "Unable to connect to Llama Stack" ,
92- "cause" : "Connection error." ,
93- }
87+ "model" : ServiceUnavailableResponse ,
88+ "description" : "Service unavailable" ,
9489 },
9590}
9691
9792conversations_list_responses : dict [int | str , dict [str , Any ]] = {
9893 200 : {
99- "conversations" : [
100- {
101- "conversation_id" : "123e4567-e89b-12d3-a456-426614174000" ,
102- "created_at" : "2024-01-01T00:00:00Z" ,
103- "last_message_at" : "2024-01-01T00:05:00Z" ,
104- "last_used_model" : "gemini/gemini-1.5-flash" ,
105- "last_used_provider" : "gemini" ,
106- "message_count" : 5 ,
107- },
108- {
109- "conversation_id" : "456e7890-e12b-34d5-a678-901234567890" ,
110- "created_at" : "2024-01-01T01:00:00Z" ,
111- "last_message_at" : "2024-01-01T01:02:00Z" ,
112- "last_used_model" : "gemini/gemini-2.0-flash" ,
113- "last_used_provider" : "gemini" ,
114- "message_count" : 2 ,
115- },
116- ]
117- },
118- 400 : {
119- "description" : "Missing or invalid credentials provided by client" ,
120- "model" : UnauthorizedResponse ,
94+ "model" : ConversationsListResponse ,
95+ "description" : "List of conversations retrieved successfully" ,
12196 },
12297 401 : {
123- "description" : "Unauthorized: Invalid or missing Bearer token" ,
12498 "model" : UnauthorizedResponse ,
99+ "description" : "Unauthorized: Invalid or missing Bearer token" ,
125100 },
126101 503 : {
127- "detail" : {
128- "response" : "Unable to connect to Llama Stack" ,
129- "cause" : "Connection error." ,
130- }
102+ "model" : ServiceUnavailableResponse ,
103+ "description" : "Service unavailable" ,
131104 },
132105}
133106
@@ -261,40 +234,46 @@ async def get_conversation_endpoint_handler(
261234 ID and simplified chat history.
262235 """
263236 check_configuration_loaded (configuration )
264-
237+
265238 # Validate conversation ID format
266239 if not check_suid (conversation_id ):
267240 logger .error ("Invalid conversation ID format: %s" , conversation_id )
268241 raise HTTPException (
269242 status_code = status .HTTP_400_BAD_REQUEST ,
270- detail = {
271- "response" : "Invalid conversation ID format" ,
272- "cause" : f"Conversation ID { conversation_id } is not a valid UUID" ,
273- },
243+ detail = BadRequestResponse (
244+ resource = "conversation" , resource_id = conversation_id
245+ ).dump_detail (),
274246 )
275247
276248 user_id = auth [0 ]
277-
278- user_conversation = validate_conversation_ownership (
279- user_id = user_id ,
280- conversation_id = conversation_id ,
249+ if not can_access_conversation (
250+ conversation_id ,
251+ user_id ,
281252 others_allowed = (
282253 Action .READ_OTHERS_CONVERSATIONS in request .state .authorized_actions
283254 ),
284- )
285-
286- if user_conversation is None :
255+ ):
287256 logger .warning (
288- "User %s attempted to read conversation %s they don't own " ,
257+ "User %s attempted to read conversation %s they don't have access to " ,
289258 user_id ,
290259 conversation_id ,
291260 )
292261 raise HTTPException (
293262 status_code = status .HTTP_403_FORBIDDEN ,
294- detail = {
295- "response" : "Access denied" ,
296- "cause" : "You do not have permission to read this conversation" ,
297- },
263+ detail = AccessDeniedResponse (
264+ user_id = user_id ,
265+ resource = "conversation" ,
266+ resource_id = conversation_id ,
267+ action = "read" ,
268+ ).dump_detail (),
269+ )
270+ conversation = retrieve_conversation (conversation_id , user_id )
271+ if conversation is None :
272+ raise HTTPException (
273+ status_code = status .HTTP_404_NOT_FOUND ,
274+ detail = NotFoundResponse (
275+ resource = "conversation" , resource_id = conversation_id
276+ ).dump_detail (),
298277 )
299278
300279 agent_id = conversation_id
@@ -308,10 +287,9 @@ async def get_conversation_endpoint_handler(
308287 logger .error ("No sessions found for conversation %s" , conversation_id )
309288 raise HTTPException (
310289 status_code = status .HTTP_404_NOT_FOUND ,
311- detail = {
312- "response" : "Conversation not found" ,
313- "cause" : f"Conversation { conversation_id } could not be retrieved." ,
314- },
290+ detail = NotFoundResponse (
291+ resource = "conversation" , resource_id = conversation_id
292+ ).dump_detail (),
315293 )
316294 session_id = str (agent_sessions [0 ].get ("session_id" ))
317295
@@ -334,22 +312,23 @@ async def get_conversation_endpoint_handler(
334312 logger .error ("Unable to connect to Llama Stack: %s" , e )
335313 raise HTTPException (
336314 status_code = status .HTTP_503_SERVICE_UNAVAILABLE ,
337- detail = {
338- "response" : "Unable to connect to Llama Stack" ,
339- "cause" : str (e ),
340- },
315+ detail = ServiceUnavailableResponse (
316+ backend_name = "Llama Stack" , cause = str (e )
317+ ).dump_detail (),
341318 ) from e
319+
342320 except NotFoundError as e :
343321 logger .error ("Conversation not found: %s" , e )
344322 raise HTTPException (
345323 status_code = status .HTTP_404_NOT_FOUND ,
346- detail = {
347- "response" : "Conversation not found" ,
348- "cause" : f"Conversation { conversation_id } could not be retrieved: { str (e )} " ,
349- },
324+ detail = NotFoundResponse (
325+ resource = "conversation" , resource_id = conversation_id
326+ ).dump_detail (),
350327 ) from e
328+
351329 except HTTPException :
352330 raise
331+
353332 except Exception as e :
354333 # Handle case where session doesn't exist or other errors
355334 logger .exception ("Error retrieving conversation %s: %s" , conversation_id , e )
@@ -389,34 +368,41 @@ async def delete_conversation_endpoint_handler(
389368 logger .error ("Invalid conversation ID format: %s" , conversation_id )
390369 raise HTTPException (
391370 status_code = status .HTTP_400_BAD_REQUEST ,
392- detail = {
393- "response" : "Invalid conversation ID format" ,
394- "cause" : f"Conversation ID { conversation_id } is not a valid UUID" ,
395- },
371+ detail = BadRequestResponse (
372+ resource = "conversation" , resource_id = conversation_id
373+ ).dump_detail (),
396374 )
397375
398376 user_id = auth [0 ]
399-
400- user_conversation = validate_conversation_ownership (
401- user_id = user_id ,
402- conversation_id = conversation_id ,
377+ if not can_access_conversation (
378+ conversation_id ,
379+ user_id ,
403380 others_allowed = (
404381 Action .DELETE_OTHERS_CONVERSATIONS in request .state .authorized_actions
405382 ),
406- )
407-
408- if user_conversation is None :
383+ ):
409384 logger .warning (
410- "User %s attempted to delete conversation %s they don't own " ,
385+ "User %s attempted to delete conversation %s they don't have access to " ,
411386 user_id ,
412387 conversation_id ,
413388 )
414389 raise HTTPException (
415390 status_code = status .HTTP_403_FORBIDDEN ,
416- detail = {
417- "response" : "Access denied" ,
418- "cause" : "You do not have permission to delete this conversation" ,
419- },
391+ detail = AccessDeniedResponse (
392+ user_id = user_id ,
393+ resource = "conversation" ,
394+ resource_id = conversation_id ,
395+ action = "delete" ,
396+ ).dump_detail (),
397+ )
398+
399+ conversation = retrieve_conversation (conversation_id , user_id )
400+ if conversation is None :
401+ raise HTTPException (
402+ status_code = status .HTTP_404_NOT_FOUND ,
403+ detail = NotFoundResponse (
404+ resource = "conversation" , resource_id = conversation_id
405+ ).dump_detail (),
420406 )
421407
422408 agent_id = conversation_id
@@ -452,25 +438,24 @@ async def delete_conversation_endpoint_handler(
452438 )
453439
454440 except APIConnectionError as e :
455- logger .error ("Unable to connect to Llama Stack: %s" , e )
456441 raise HTTPException (
457442 status_code = status .HTTP_503_SERVICE_UNAVAILABLE ,
458- detail = {
459- "response" : "Unable to connect to Llama Stack" ,
460- "cause" : str (e ),
461- },
443+ detail = ServiceUnavailableResponse (
444+ backend_name = "Llama Stack" , cause = str (e )
445+ ).dump_detail (),
462446 ) from e
447+
463448 except NotFoundError as e :
464- logger .error ("Conversation not found: %s" , e )
465449 raise HTTPException (
466450 status_code = status .HTTP_404_NOT_FOUND ,
467- detail = {
468- "response" : "Conversation not found" ,
469- "cause" : f"Conversation { conversation_id } could not be deleted: { str (e )} " ,
470- },
451+ detail = NotFoundResponse (
452+ resource = "conversation" , resource_id = conversation_id
453+ ).dump_detail (),
471454 ) from e
455+
472456 except HTTPException :
473457 raise
458+
474459 except Exception as e :
475460 # Handle case where session doesn't exist or other errors
476461 logger .exception ("Error deleting conversation %s: %s" , conversation_id , e )
0 commit comments