# Alexa Suno Skill Architecture Document

## 1. Introduction

This document outlines the architecture of the Alexa Suno Skill, which integrates Suno AI music generation with the Alexa voice platform. Users can generate music, browse their Suno library, and play content through Alexa-enabled devices.

## 2. High-Level Architecture

The skill operates through three main entities:

1.  **Alexa Platform**: Handles voice input from the user, manages skill interactions, and renders audio and visual responses on Alexa devices.
2.  **AWS Lambda Function (`lambda_function.py`)**: The backend logic for the Alexa skill. It processes requests from Alexa, interacts with the Suno API, and formats responses back to Alexa.
3.  **Suno API (`suno.py`)**: The external service provided by Suno AI. It handles music generation, library management, content streaming, and account-related operations.

```mermaid
graph TD
    A[User via Alexa Device] --> B{Alexa Platform};
    B --> C[AWS Lambda: lambda_function.py];
    C --> D[Suno API: suno.py];
    D --> E[Suno AI Backend Services];
    C --> B;
    D --> C;
```

**Component Roles:**

*   **Alexa Platform**:
    *   Receives voice commands from the user.
    *   Transforms voice commands into structured JSON requests (directives) sent to the Lambda function.
    *   Manages user sessions and skill lifecycle events (e.g., account linking).
    *   Handles playback of audio content provided by the skill.
    *   Displays visual metadata on screen-enabled devices.
*   **AWS Lambda Function (`lambda_function.py`)**:
    *   Serves as the main entry point for all Alexa requests.
    *   Parses incoming Alexa directives.
    *   Routes requests to appropriate handlers based on directive namespace and name.
    *   Orchestrates interactions with the Suno API via the `suno.py` module.
    *   Formats responses according to Alexa interface specifications (e.g., `Alexa.Media.Search.Response`, `Alexa.Audio.PlayQueue.Response`).
    *   Manages error handling and constructs appropriate Alexa error responses.
    *   Implements logging for debugging and monitoring.
*   **Suno API (`suno.py` wrapper)**:
    *   Provides a Python interface to the Suno AI backend services.
    *   Handles authentication with the Suno API using user-specific access tokens.
    *   Makes HTTP requests to various Suno API endpoints for:
        *   Content generation.
        *   Fetching clip details and metadata.
        *   Searching the user's Suno library.
        *   Managing play queues.
        *   Handling Server-Sent Events (SSE) for real-time updates (e.g., generation progress, title extraction).
        *   Notifying Suno backend of account linking and catalog updates.
        *   Logging playback events.
    *   Includes error handling for API requests and SSE stream processing.

## 3. Core Components

### 3.1. `lambda_function.py`

This is the main backend for the Alexa skill.

*   **Role**: Handles requests and orchestrates API calls.
*   **Responsibilities**:
    *   **Entry Point (`lambda_handler`)**: Receives all events from Alexa. Sets up request-specific logging.
    *   **Request Parsing & Routing**: Uses pattern matching to identify the type of Alexa directive (e.g., `Alexa.Audio.SyncUserContent`, `Alexa.Media.Search`, `AlexaSkillEvent.SkillAccountLinked`).
    *   **Directive Processing**: Calls specific handler functions (e.g., `process_alexa_directive`, `handle_alexa_audio_playqueue`, `handle_alexa_media_search`, `process_initiate_playback`).
    *   **Error Handling**: Implements a try-except block to catch skill errors (`SkillError` and its subclasses) and general exceptions, converting them into Alexa error responses using `build_alexa_error_response` or `build_media_error_response`.
    *   **Response Generation**: Constructs JSON responses compliant with Alexa's various interfaces.
    *   **Latency Management**: Includes logic to manage processing time, especially for content generation, to meet Alexa's response time requirements. A global `_real_processing_end_time` variable is used to track actual processing time before artificial delays.

**Flow of `lambda_handler`:**

```mermaid
graph TD
    Start[Lambda Invocation] --> Initialize[Initialize: Reset Logs, Timers];
    Initialize --> ParseEvent[Parse Event & Identify Directive];
    ParseEvent -- Alexa.Audio.SyncUserContent --> SyncHandler[Handle SyncUserContent];
    ParseEvent -- AlexaSkillEvent.SkillAccountLinked --> AccountLinkedHandler[Handle SkillAccountLinked];
    ParseEvent -- AlexaAudioPlayQueueEvent --> PlaybackEventHandler[Handle Playback Event];
    ParseEvent -- API Gateway Body (JSON) --> ProcessBody[Parse JSON Body & Call process_alexa_directive];
    ParseEvent -- Direct Alexa Directive --> CallProcessAlexaDirective[Call process_alexa_directive];
    ParseEvent -- Unrecognized --> UnrecognizedHandler[Handle Unrecognized Event];

    SyncHandler --> BuildResponse[Build Success/Error Response];
    AccountLinkedHandler --> BuildResponse;
    PlaybackEventHandler --> BuildResponse;
    ProcessBody --> BuildResponse;
    CallProcessAlexaDirective --> BuildResponse;
    UnrecognizedHandler --> BuildResponse;

    subgraph Error Handling
        direction LR
        GlobalTry{Try} --> CaughtException{Catch Exception?};
        CaughtException -- Yes --> BuildErrorResponse[Build Specific Error Response e.g., ContentNotFoundError];
        CaughtException -- No --> NormalFlow[Continue Normal Flow];
        BuildErrorResponse --> FinalResponse;
        NormalFlow --> FinalResponse;
    end

    BuildResponse --> LogMetrics[Log Metrics: Duration, Status];
    LogMetrics --> FinalResponse[Return Response to Alexa];

    ProcessBody -.-> GlobalTry;
    CallProcessAlexaDirective -.-> GlobalTry;
    SyncHandler -.-> GlobalTry;
    AccountLinkedHandler -.-> GlobalTry;
    PlaybackEventHandler -.-> GlobalTry;
    UnrecognizedHandler -.-> GlobalTry;
```

### 3.2. `suno.py`

This module encapsulates all interactions with the Suno AI backend.

*   **Role**: Abstraction layer for the Suno API.
*   **Key Responsibilities**:
    *   **API Client Logic**: Makes HTTP GET/POST requests to Suno endpoints (e.g., `/clips`, `/search-user-clips`, `/generate`, `/alexa/skill-account-linked`).
    *   **Authentication**: Adds `Authorization: Bearer <access_token>` headers to requests.
    *   **Data Serialization/Deserialization**: Handles JSON request bodies and parses JSON responses.
    *   **SSE Handling**:
        *   `connect_to_sse_stream`: Generic function to connect to an SSE stream and process events.
        *   `get_title_and_tags_from_sse`: Fetches title and tags for a generating clip.
        *   `wait_for_clip_events`: Waits for critical events like `image_generated` and `gen_streaming` for a clip.
    *   **Error Mapping**: Catches `requests.exceptions` and maps HTTP status codes (401, 402, 429) to `SkillError` subclasses (`AuthorizationError`, `RateLimitError`, `ContentFilteredError`).
    *   **Content Formatting**: Prepares data for specific Alexa responses (e.g., `generate_art_sources`, `get_art_url_for_item`).
    *   **Utility Functions**: Provides helpers like `get_clip_details`, `search_suno_library`, `notify_skill_account_linked`, `log_playback_event`.

**Interaction between `lambda_function.py` and `suno.py` (Example: Content Generation)**

```mermaid
sequenceDiagram
    participant LF as lambda_function.py
    participant SP as suno.py
    participant SunoAPI as Suno AI Backend

    LF->>SP: handle_generate_content_action(token, prompt, startTime)
    SP->>SP: call_suno_generate_api(token, prompt, isInstrumental)
    SP->>SunoAPI: POST /api/v2/external/oauth/generate
    SunoAPI-->>SP: JSON response (clip_id, request_id)
    SP->>SP: get_title_and_tags_from_sse(request_id, timeout)
    SP->>SunoAPI: GET /request_events/?request_id=... (SSE Stream)
    SunoAPI-->>SP: SSE events (title, tags)
    SP-->>LF: {clip_id, title, image_url}
```

### 3.3. `error_models.py`

*   **Role**: Defines custom exception classes for skill-specific errors and helper functions to build standardized Alexa error responses.
*   **Key Components**:
    *   `SkillError` (base class) and subclasses: `AuthorizationError`, `ContentNotFoundError`, `ContentFilteredError`, `InvalidRequestError`, `RateLimitError`, `GeoRestrictionError`. These allow for more granular error handling and reporting.
    *   `build_alexa_error_response()`: Constructs a generic Alexa error response.
    *   `build_media_error_response()`: Constructs an error response specifically for media-related errors, often used with `Alexa.Media.Search` interface.

### 3.4. `constants.py`

*   **Role**: Stores global constants like API URLs, timeouts, genre lists, etc.
*   **Benefit**: Centralizes configuration and improves maintainability.

### 3.5. `instrumental_determinator.py`

*   **Role**: Contains logic (`song_is_vocal`) to determine if a song generation request should produce an instrumental track based on keywords in the user's prompt.

## 4. Key Workflows

### 4.1. Account Linking & Content Sync

**Alexa.Audio.SyncUserContent / AlexaSkillEvent.SkillAccountLinked**

This flow is triggered when a user links their Suno account to the Alexa skill or when Alexa requests a content catalog sync.

```mermaid
sequenceDiagram
    participant Alexa
    participant Lambda as lambda_function.py
    participant SunoPy as suno.py
    participant SunoAPI as Suno AI Backend

    alt SkillAccountLinked Event
        Alexa->>Lambda: AlexaSkillEvent.SkillAccountLinked (userId, accessToken)
        Lambda->>SunoPy: notify_skill_account_linked(accessToken, userId, previousSyncToken)
        SunoPy->>SunoAPI: POST /alexa/skill-account-linked (params: alexa_user_id, previous_sync_token)
        SunoAPI-->>SunoPy: {sync_info: {needs_full_update, has_no_content, sync_token}} or success
        SunoPy-->>Lambda: Sync info or success/failure
        Lambda-->>Alexa: 200 OK or 500 Error
    else SyncUserContent Directive
        Alexa->>Lambda: Alexa.Audio.SyncUserContent (userId, accessToken, previousSyncToken)
        Lambda->>SunoPy: notify_skill_account_linked(accessToken, userId, previousSyncToken)
        SunoPy->>SunoAPI: POST /alexa/skill-account-linked (params: alexa_user_id, previous_sync_token)
        SunoAPI-->>SunoPy: {sync_info: {needs_full_update, has_no_content, sync_token}}
        SunoPy-->>Lambda: Sync info or error
        Lambda->>Lambda: Determine syncStatus (ASYNC_UPDATE, NO_CONTENT, NO_UPDATE)
        Lambda-->>Alexa: SyncUserContent.Response (syncStatus)
    end
```

**Explanation:**
1.  User links their account, or Alexa initiates a sync.
2.  Lambda receives the event/directive containing user ID and access token.
3.  `lambda_function.py` calls `suno.py:notify_skill_account_linked`.
4.  `suno.py` calls the Suno API's `/alexa/skill-account-linked` endpoint. This endpoint informs Suno that a user has linked their account and might trigger a catalog sync process on Suno's side.
5.  Suno API returns sync status information (e.g., if a full update is needed, if the user has no content).
6.  For `SyncUserContent`, Lambda constructs a response indicating the `syncStatus` (e.g., `ASYNC_UPDATE` if Suno needs to send catalog updates, `NO_CONTENT`, or `NO_UPDATE`).

### 4.2. Content Generation (GENERATE_CONTENT Action)

**Alexa.Media.Search (GetPlayableContent / GetDisplayableContent with `action: GENERATE_CONTENT`)**

This workflow handles requests to generate new music.

```mermaid
sequenceDiagram
    participant Alexa
    participant Lambda as lambda_function.py
    participant SunoPy as suno.py
    participant SunoAPI as Suno AI Backend

    Alexa->>Lambda: GetPlayableContent/GetDisplayableContent Request (action: GENERATE_CONTENT, prompt)
    Lambda->>Lambda: Identify GENERATE_CONTENT action, extract prompt
    Lambda->>SunoPy: handle_generate_content_action(accessToken, prompt, handlerStartTime)
    SunoPy->>SunoPy: song_is_vocal(prompt)
    SunoPy->>SunoPy: call_suno_generate_api(accessToken, cleaned_prompt, make_instrumental)
    SunoPy->>SunoAPI: POST /generate (topic, model, make_instrumental)
    SunoAPI-->>SunoPy: JSON Response (clip_id, request_id, image_url)
    opt If request_id present
        SunoPy->>SunoPy: get_title_and_tags_from_sse(request_id, dynamic_timeout)
        SunoPy->>SunoAPI: GET /request_events/ (SSE stream for title/tags)
        SunoAPI-->>SunoPy: SSE Events (title, tags)
        SunoPy-->>SunoPy: Title extracted
    end
    SunoPy-->>Lambda: {clip_id, title, image_url}
    Lambda->>Lambda: Set _real_processing_end_time
    Lambda->>Lambda: Potentially sleep to meet MIN_GENERATE_CONTENT_EXECUTION_TIME_SECONDS
    Lambda->>Lambda: build_media_search_playable_content_response / build_media_search_displayable_content_response
    Lambda-->>Alexa: GetPlayableContent.Response / GetDisplayableContent.Response (with generated item)
```

**Explanation:**
1.  User asks Alexa to create a song (e.g., "Alexa, ask Suno to make a blues song about rain").
2.  Alexa sends a `GetPlayableContent` or `GetDisplayableContent` directive with an `action` of type `GENERATE_CONTENT` and the prompt in `rankedSelectionCriteria`.
3.  Lambda's `process_media_search` identifies the `GENERATE_CONTENT` action.
4.  It calls `suno.py:handle_generate_content_action`.
5.  `suno.py` determines if the song should be instrumental, then calls `call_suno_generate_api` to request generation from Suno.
6.  Suno API returns initial clip information, including a `clip_id` and `request_id`.
7.  If a `request_id` is available, `suno.py` uses `get_title_and_tags_from_sse` to listen to an SSE stream for the generated title and tags. This happens within a dynamic time budget.
8.  `suno.py` returns the `clip_id`, extracted (or fallback) `title`, and an `image_url` to Lambda.
9.  Lambda records the actual processing time. If the processing was too fast, it might sleep briefly to ensure a minimum perceived generation time.
10. Lambda builds and returns the appropriate media search response containing the newly generated track's metadata.

### 4.3. Content Search & Playback (GetPlayableContent / GetDisplayableContent - No Generation)

**Alexa.Media.Search (GetPlayableContent / GetDisplayableContent)**

This workflow handles searching for existing content in the user's Suno library.

```mermaid
sequenceDiagram
    participant Alexa
    participant Lambda as lambda_function.py
    participant SunoPy as suno.py
    participant SunoAPI as Suno AI Backend

    Alexa->>Lambda: GetPlayableContent/GetDisplayableContent Request (criteria, filters)
    Lambda->>Lambda: process_media_search(request, handlerStartTime)
    Lambda->>Lambda: Loop through rankedSelectionCriteria
    Lambda->>SunoPy: handle_attribute_query(criteria, filters, accessToken, request_name, max_results)
    SunoPy->>SunoPy: search_suno_library(accessToken, search_term, page_size) or get_clip_details(accessToken, track_id)
    SunoPy->>SunoAPI: POST /search (library_song, term) or GET /clips?ids=...
    SunoAPI-->>SunoPy: Search results / Clip details
    SunoPy-->>Lambda: List of matching items or single item
    opt If GetDisplayableContent
        Lambda->>Lambda: build_media_search_displayable_content_response(messageId, results, criteriaId)
    else If GetPlayableContent and results found
        Lambda->>Lambda: build_media_search_playable_content_response(messageId, results[0], criteriaId)
    else
        Lambda->>Lambda: build_media_error_response (CONTENT_NOT_FOUND)
    end
    Lambda-->>Alexa: Response (content or error)
```

**Explanation:**
1.  User asks to play or browse content (e.g., "Alexa, play my songs on Suno," or "Alexa, show my rock songs on Suno").
2.  Alexa sends a `GetPlayableContent` (for direct playback) or `GetDisplayableContent` (for browsing) directive.
3.  Lambda's `process_media_search` iterates through `rankedSelectionCriteria`.
4.  For each criterion, `handle_attribute_query` is called, which may in turn call `suno.py:search_suno_library` (for general searches/genre) or `suno.py:get_clip_details` (if a specific track ID is identified).
5.  `suno.py` interacts with the Suno API (`/search` or `/clips`).
6.  Suno API returns search results or clip details.
7.  Lambda formats the results into the appropriate response (`GetPlayableContent.Response` or `GetDisplayableContent.Response`) or an error if no content is found.

### 4.4. Playback Initiation

**Alexa.Media.Playback.Initiate**

This directive is sent when Alexa needs to start playing a specific track, usually one selected from a `GetPlayableContent` response.

```mermaid
sequenceDiagram
    participant Alexa
    participant Lambda as lambda_function.py
    participant SunoPy as suno.py
    participant SunoAPI as Suno AI Backend

    Alexa->>Lambda: Initiate Request (contentId, accessToken)
    Lambda->>Lambda: process_initiate_playback(request)
    Lambda->>SunoPy: build_play_queue_item(contentId, messageId, null, accessToken, enable_next=true, enable_previous=false)
    SunoPy->>SunoPy: get_clip_details(accessToken, contentId) (if details not passed)
    SunoPy->>SunoAPI: GET /clips?ids=...
    SunoAPI-->>SunoPy: Clip details (title, audio_url, image_url, duration, status)
    opt If clip status is not 'complete' or 'submitted'
        SunoPy->>SunoPy: wait_for_clip_events(contentId, handlerStartTime)
        SunoPy->>SunoAPI: GET /clip_events/?clip_id=... (SSE for image_generated, gen_streaming)
        SunoAPI-->>SunoPy: SSE Events
        SunoPy-->>SunoPy: Image and audio confirmed ready, image_url updated
    end
    SunoPy->>SunoPy: Format item_metadata, item_stream, controls
    SunoPy-->>Lambda: firstItem (PlayQueueItem structure)
    Lambda->>Lambda: Construct Initiate.Response payload (queueId, firstItem, controls)
    Lambda->>Lambda: Set _real_processing_end_time
    Lambda->>Lambda: Potentially sleep if not a complete song and processing was too fast
    Lambda-->>Alexa: Initiate.Response
```

**Explanation:**
1.  After a `GetPlayableContent` response, Alexa decides to play the item and sends an `Initiate` directive with the `contentId`.
2.  Lambda's `process_initiate_playback` handles this.
3.  It calls `suno.py:build_play_queue_item` to construct the first item for the Alexa audio player queue.
4.  `build_play_queue_item`:
    *   Fetches full clip details from Suno API (`get_clip_details`) if not already provided.
    *   If the clip is still generating (`status` not 'complete' or 'submitted'), it calls `wait_for_clip_events` to monitor an SSE stream from Suno to confirm the audio stream and image are ready. This ensures that Alexa doesn't try to play an incomplete item.
    *   Constructs the `PlayQueueItem` with metadata (title, art), stream information (audio URL, expiry), and controls. For `Initiate`, 'PREVIOUS' is disabled for the first item.
5.  Lambda receives the formatted `firstItem`.
6.  Lambda builds the `Initiate.Response`, including a `queueId` and the `firstItem`.
7.  Similar to generation, it may add a delay if the song is not yet complete and processing was very fast.

### 4.5. Play Queue Navigation

**Alexa.Audio.PlayQueue (GetNextItem / GetPreviousItem)**

This handles requests to navigate to the next or previous song in the queue.

```mermaid
sequenceDiagram
    participant Alexa
    participant Lambda as lambda_function.py
    participant SunoPy as suno.py
    participant SunoAPI as Suno AI Backend

    Alexa->>Lambda: GetNextItem/GetPreviousItem Request (currentItemReference, accessToken)
    Lambda->>Lambda: handle_alexa_audio_playqueue(directive_name, messageId, payload, handlerStartTime)
    Lambda->>SunoPy: get_item_in_queue(accessToken, current_item_id, handlerStartTime, is_next_item, limit=2, return_list=True)
    SunoPy->>SunoAPI: GET /search-user-clips? (before_id/after_id, limit=2)
    SunoAPI-->>SunoPy: List of 0, 1, or 2 clips
    SunoPy-->>Lambda: API response (list of clips)
    Lambda->>Lambda: Determine has_more_items, select next/previous item from list
    Lambda->>SunoPy: build_play_queue_item(selected_clip_id, messageId, selected_clip_details, accessToken, enable_next, enable_previous)
    SunoPy->>SunoPy: (Logic similar to Initiate to fetch/wait for clip details if needed)
    SunoPy-->>Lambda: playQueueItem
    Lambda->>Lambda: build_play_queue_item_response(messageId, directive_name, playQueueItem, isQueueFinished)
    Lambda-->>Alexa: GetNextItem.Response / GetPreviousItem.Response
```

**Explanation:**
1.  User requests "Next song" or "Previous song."
2.  Alexa sends `GetNextItem` or `GetPreviousItem` directive.
3.  Lambda's `handle_alexa_audio_playqueue` calls `suno.py:get_item_in_queue`.
    *   Crucially, `limit=2` and `return_list=True` are used. Requesting two items allows the skill to determine if there are *more* items available beyond the immediate next/previous one, which is used to enable/disable the Next/Previous buttons in the Alexa UI.
4.  `suno.py` calls Suno's `/search-user-clips` endpoint with `before_id` (for next) or `after_id` (for previous) and `limit=2`.
5.  Suno API returns a list of up to two clips.
6.  Lambda processes this list:
    *   The first item in the list is the one to be played.
    *   The presence of a second item indicates that `has_more_items` is true in that direction.
7.  Lambda then calls `suno.py:build_play_queue_item` for the selected clip to get its full details (similar to the `Initiate` flow, including waiting for generation if necessary). Navigation controls (enable_next, enable_previous) are set based on `has_more_items`.
8.  Finally, Lambda returns the `GetNextItem.Response` or `GetPreviousItem.Response` with the new `PlayQueueItem`.

### 4.6. Playback Events

**AlexaAudioPlayQueueEvent (ItemPlaybackStarted, ItemPlaybackFailed, ItemPlaybackFinished, ItemPlaybackStopped)**

Alexa sends these events asynchronously to inform the skill about the playback status.

```mermaid
sequenceDiagram
    participant Alexa
    participant Lambda as lambda_function.py
    participant SunoPy as suno.py
    participant SunoAPI as Suno AI Backend

    Alexa->>Lambda: AlexaAudioPlayQueueEvent (e.g., ItemPlaybackStarted, contentId, queueId, offsetInMilliseconds, accessToken)
    Lambda->>Lambda: handle_playback_event(event_data, event_type)
    Lambda->>SunoPy: log_playback_event(accessToken, userId, contentId, queueId, event_type, offset_ms, error_type, ...)
    SunoPy->>SunoAPI: POST /alexa/playback-event (event details)
    SunoAPI-->>SunoPy: 200 OK (response ignored by SunoPy)
    SunoPy-->>Lambda: (Returns None)
    Lambda-->>Alexa: 200 OK (empty body)
```

**Explanation:**
1.  During playback, Alexa sends events like `ItemPlaybackStarted`, `ItemPlaybackFinished`, etc.
2.  Lambda's `handle_playback_event` extracts relevant information from the event payload.
3.  It calls `suno.py:log_playback_event`.
4.  `suno.py` sends a POST request to Suno API's `/alexa/playback-event` endpoint. This allows Suno to track playback analytics and user history.
5.  The Suno API call is fire-and-forget from Lambda's perspective.
6.  Lambda returns a simple `200 OK` to Alexa, as Alexa does not expect a detailed payload for these events.

## 5. Error Handling

*   **Custom Errors (`error_models.py`)**: Custom exception classes (e.g., `ContentNotFoundError`, `AuthorizationError`) inherit from `SkillError`. This allows error conditions from the Suno API or internal logic to be caught and handled.
*   **Centralized Handling (`lambda_handler`)**: The main `lambda_handler` has a `try...except` block that catches these custom errors as well as generic `Exception`.
*   **Response Builders (`error_models.py`)**:
    *   `build_alexa_error_response`: Creates a standard Alexa error response (e.g., `INTERNAL_ERROR`, `INVALID_DIRECTIVE`).
    *   `build_media_error_response`: Creates errors for media interfaces (e.g., `CONTENT_NOT_FOUND`, `CONTENT_FILTERED`). These can include subtypes.
*   **Logging**: Errors are logged with details, including stack traces for unhandled exceptions, to aid debugging. The `messageId` from the Alexa request is included in logs for better traceability.

## 6. Logging

*   **Standard Python Logging**: The project uses Python's built-in `logging` module.
*   **`MessageIdFormatter`**: A custom log formatter that prefixes log messages with the `messageId` from the Alexa request. This helps with debugging and correlating logs for specific user interactions.
    *   `set_message_id_for_logs()` and `reset_message_id_for_logs()` are used to manage the formatter's state within each `lambda_handler` invocation.
*   **Log Levels**: `INFO` level is generally used, with `DEBUG` for more verbose output and `WARNING`/`ERROR` for issues.
*   **Output**: Logs are sent to AWS CloudWatch Logs.
*   **Key Information Logged**:
    *   Incoming request object.
    *   Directive namespace and name.
    *   Handler duration (real and full).
    *   Status (success/error).
    *   Outgoing response object.
    *   Specific errors and warnings from API calls or business logic.

## 7. Directory Structure (Assumed)

```
.
├── lambda_function.py        # Main AWS Lambda handler
├── suno.py                   # Suno API interaction module
├── error_models.py           # Custom error classes and builders
├── constants.py              # Project constants
├── instrumental_determinator.py # Logic for instrumental detection
├── requirements.txt          # Python dependencies
└── ARCHITECTURE.md           # This document
```

## 8. Future Considerations

*   **State Management**: Persistent storage (e.g., DynamoDB) for user preferences.
*   **Search**: Support for complex predicate trees from Alexa.
*   **Internationalization**: Multiple language support.
*   **Testing**: Unit and integration tests.
*   **CI/CD**: Automated deployment pipeline.
*   **SSE Timeouts**: Timeout optimization based on event types.
*   **Caching**: Cache frequently accessed API data where applicable.

As the project evolves, this document should be updated to reflect changes in architecture and functionality. 

## 9. Testing

### Quick Start
```bash
# Configure AWS SSO
./config_sso.sh us-east-1

# Install dependencies
uv sync

# Run interactive tests
uv run test_lambda.py
```

### Test Options
1. Generate songs with custom prompts
2. Test playback of existing songs
3. Test queue navigation (next/previous)
4. Browse and search library
5. Full flow test (Create → Play → Navigate)
6. Repeat mode testing

### Debugging
- `./tail.sh` - Monitor CloudWatch logs
- `./dump_logs.sh` - Export logs for analysis
- Request examples in `alexa/request_examples/`
- Captured requests saved to `aws_console_requests/`

For detailed testing documentation, see `CLAUDE.md`.

## OAuth

Endpoints in bots/external/api.py that are special (not default django-oauth-toolkit)
```
@router.get("/v2/external/oauth/authorize")
@router.post("/v2/external/oauth/generate-code", auth=AuthBearer(), response=OAuthRedirectResponseSchema)
```

Get info about the application, like its name based on the clientId 
```
@router.get("/v2/external/oauth/client-info", auth=AuthBearer(), response=OAuthClientInfoSchema)
```
Django management command to create the application
```
uv run manage.py create_oauth_client \
    --name "Alexa" \
    --redirect-uris "https://pitangui.amazon.com/api/skill/link/M211LAR57QLT8O https://alexa.amazon.co.jp/api/skill/link/M211LAR57QLT8O https://layla.amazon.com/api/skill/link/M211LAR57QLT8O http://localhost:3001/oauth_callback http://tldw.media:3001/oauth_callback" \
    --scopes "read_profile read_music generate_music" \
    --pkce "disabled"

Successfully created OAuth client: Alexa
Client ID: suno-b..............Q
Client Secret: q.............................U
Redirect URIs: https://pitangui.amazon.com/api/skill/link/M211LAR57QLT8O https://alexa.amazon.co.jp/api/skill/link/M211LAR57QLT8O https://layla.amazon.com/api/skill/link/M211LAR57QLT8O http://localhost:3001/oauth_callback http://tldw.media:3001/oauth_callback
PKCE: Disabled

OAuth Configuration Instructions:
- Authorization URL: {your_domain}/api/v2/external/oauth/authorize/ (trailing slash required)
- Token URL: {your_domain}/api/v2/external/oauth/token/
- Client Authentication Scheme: HTTP Basic (Recommended)
- Scope: read_profile generate_music read_music    
```

The redirect uris and scopes can be change later in the oauth2_providers_application database table 

