Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-12 09:36:17

0001 """Snapper episode REST adapters (snapper-ai docs/EPISODES.md).
0002 
0003 Thin transports over ``snapper_ai.episodes``. The write endpoints are
0004 token-authenticated — the episode builder agent posts open, append,
0005 and close with its builder identity. The read endpoints are read-open
0006 like the monitor's other read surfaces; errors are explicit JSON.
0007 """
0008 
0009 from dataclasses import asdict
0010 
0011 from django.http import JsonResponse
0012 from rest_framework.authentication import (SessionAuthentication,
0013                                            TokenAuthentication)
0014 from rest_framework.decorators import (api_view, authentication_classes,
0015                                        permission_classes)
0016 from rest_framework.permissions import IsAuthenticated
0017 
0018 from snapper_ai.episodes import (BuilderNotAuthorized, EpisodeClosed,
0019                                  EpisodeNotFound, InvalidEpisode,
0020                                  append_events, close_episode,
0021                                  episode_record, list_episodes,
0022                                  open_episode)
0023 
0024 
0025 def _run_write(call):
0026     try:
0027         update = call()
0028     except InvalidEpisode as e:
0029         return JsonResponse({'error': str(e)}, status=400)
0030     except EpisodeNotFound as e:
0031         return JsonResponse({'error': str(e)}, status=404)
0032     except EpisodeClosed as e:
0033         return JsonResponse({'error': str(e)}, status=409)
0034     except BuilderNotAuthorized as e:
0035         return JsonResponse({'error': str(e)}, status=403)
0036     return JsonResponse(asdict(update))
0037 
0038 
0039 @api_view(['POST'])
0040 @authentication_classes([TokenAuthentication, SessionAuthentication])
0041 @permission_classes([IsAuthenticated])
0042 def episodes_open(request):
0043     """POST /api/snapper/episodes/open/"""
0044     body = request.data
0045     return _run_write(lambda: open_episode(
0046         scope=body.get('scope'),
0047         episode_id=body.get('episode_id'),
0048         builder_identity=body.get('builder_identity'),
0049         started_at=body.get('started_at'),
0050         label=body.get('label') or '',
0051         kind=body.get('kind') or '',
0052         summary=body.get('summary'),
0053     ))
0054 
0055 
0056 @api_view(['POST'])
0057 @authentication_classes([TokenAuthentication, SessionAuthentication])
0058 @permission_classes([IsAuthenticated])
0059 def episodes_append(request):
0060     """POST /api/snapper/episodes/append/"""
0061     body = request.data
0062     return _run_write(lambda: append_events(
0063         scope=body.get('scope'),
0064         episode_id=body.get('episode_id'),
0065         builder_identity=body.get('builder_identity'),
0066         events=body.get('events'),
0067         participants=body.get('participants'),
0068     ))
0069 
0070 
0071 @api_view(['POST'])
0072 @authentication_classes([TokenAuthentication, SessionAuthentication])
0073 @permission_classes([IsAuthenticated])
0074 def episodes_close(request):
0075     """POST /api/snapper/episodes/close/"""
0076     body = request.data
0077     return _run_write(lambda: close_episode(
0078         scope=body.get('scope'),
0079         episode_id=body.get('episode_id'),
0080         builder_identity=body.get('builder_identity'),
0081         ended_at=body.get('ended_at'),
0082         summary=body.get('summary'),
0083     ))
0084 
0085 
0086 def episodes_list_view(request, scope):
0087     """GET /api/snapper/<scope>/episodes/?limit=N"""
0088     try:
0089         limit = int(request.GET.get('limit') or 50)
0090     except ValueError:
0091         return JsonResponse({'error': 'limit must be an integer'}, status=400)
0092     try:
0093         episodes = list_episodes(scope, limit=limit)
0094     except InvalidEpisode as e:
0095         return JsonResponse({'error': str(e)}, status=400)
0096     return JsonResponse({'count': len(episodes), 'episodes': episodes},
0097                         json_dumps_params={'default': str})
0098 
0099 
0100 def episode_detail_view(request, scope, episode_id):
0101     """GET /api/snapper/<scope>/episodes/<episode_id>/"""
0102     try:
0103         record = episode_record(scope, episode_id)
0104     except InvalidEpisode as e:
0105         return JsonResponse({'error': str(e)}, status=400)
0106     except EpisodeNotFound as e:
0107         return JsonResponse({'error': str(e)}, status=404)
0108     return JsonResponse(record, json_dumps_params={'default': str})