Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Changelog

All notable changes to this project will be documented in this file.

## [0.1.0] - 2025-12-02

### Added

- Full support for all LayerCode webhook event types: `session.start`, `message`, `data`, `session.update`, `session.end`
- New `outdoor_shop` agent: A customer service agent for the fictional "Nimbus Gear" outdoor equipment store with more complex tool responses (to mimic `stream.data` payloads)


## [0.0.1] - 2025-11-02

### Added

- Initial release
- Core SDK with `StreamHelper` for building agents
- Built-in agents: `echo`, `starter`, `bakery`
- FastAPI server with webhook handling
- Cloudflare tunnel integration
- CLI with `run` and `list-agents` commands
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,22 @@ All prompts live under `src/layercode_create_app/agents/prompts/`. Edit them or

---

## Webhook Events

The SDK handles all LayerCode webhook event types:

| Event | Description | Agent Method |
|-------|-------------|--------------|
| `session.start` | New session begins | `handle_session_start()` |
| `message` | User speech transcribed | `handle_message()` |
| `data` | Client-sent structured JSON | `handle_data()` |
| `session.update` | Recording completed/failed | `handle_session_update()` |
| `session.end` | Session finished with transcript | `handle_session_end()` |

Override these methods in your agent to handle each event type. See the [SDK documentation](https://svilupp.github.io/layercode-create-app/api/sdk/) for payload schemas.

---

## Cloudflare Tunnel

When you use the `--tunnel` flag:
Expand Down
144 changes: 127 additions & 17 deletions docs/api/sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,140 @@ The LayerCode SDK provides primitives for webhook handling, signature verificati

### `layercode_create_app.sdk.events`

Event types and models for LayerCode webhooks.
Typed event models for LayerCode webhooks. All models are Pydantic-based with full type hints.

#### `LayercodeEvent`
#### Event Types

Main event model representing incoming webhooks.
The SDK supports all LayerCode webhook events:

| Event | Payload Class | Has `turn_id` | Description |
|-------|---------------|---------------|-------------|
| `session.start` | `SessionStartPayload` | Yes | New session begins |
| `message` | `MessagePayload` | Yes | User speech transcribed |
| `data` | `DataPayload` | Yes | Client-sent structured JSON |
| `session.update` | `SessionUpdatePayload` | No | Recording completed/failed |
| `session.end` | `SessionEndPayload` | No | Session finished with transcript |

#### `SessionStartPayload`

Sent when a new voice session begins.

```python
from layercode_create_app.sdk import SessionStartPayload

class SessionStartPayload:
type: Literal["session.start"]
session_id: str
conversation_id: str
turn_id: str
text: str | None = None
metadata: dict | None = None
from_phone_number: str | None = None
to_phone_number: str | None = None
```

#### `MessagePayload`

Sent when user speech is transcribed.

```python
from layercode_create_app.sdk.events import LayercodeEvent

class LayercodeEvent:
type: str # Event type (call_started, transcript, etc.)
call_id: str # Unique call identifier
agent_id: str # Agent identifier
timestamp: datetime # Event timestamp
transcript: str | None # User transcript (for transcript events)
metadata: dict # Additional event metadata
from layercode_create_app.sdk import MessagePayload

class MessagePayload:
type: Literal["message"]
session_id: str
conversation_id: str
turn_id: str
text: str | None = None
recording_url: str | None = None
recording_status: str | None = None
transcript: str | None = None
usage: dict | None = None
metadata: dict | None = None
from_phone_number: str | None = None
to_phone_number: str | None = None
```

**Event Types:**
#### `DataPayload`

Sent when the client emits structured JSON (e.g., button clicks, form data).

```python
from layercode_create_app.sdk import DataPayload

class DataPayload:
type: Literal["data"]
session_id: str
conversation_id: str
turn_id: str
data: dict # Arbitrary JSON payload from client
metadata: dict | None = None
from_phone_number: str | None = None
to_phone_number: str | None = None
```

#### `SessionUpdatePayload`

Sent when asynchronous session data becomes available (e.g., recording completion).

- `call_started` - Call has begun
- `transcript` - User speech transcribed
- `call_ended` - Call has ended
- `user_interrupted` - User interrupted the agent
```python
from layercode_create_app.sdk import SessionUpdatePayload

class SessionUpdatePayload:
type: Literal["session.update"]
session_id: str
conversation_id: str
recording_status: str | None = None # "completed" | "failed"
recording_url: str | None = None # URL to download WAV
recording_duration: float | None = None # Duration in seconds
error_message: str | None = None # Details when failed
metadata: dict | None = None
from_phone_number: str | None = None
to_phone_number: str | None = None
```

#### `SessionEndPayload`

Sent when the session finishes, includes full transcript and metrics.

```python
from layercode_create_app.sdk import SessionEndPayload, TranscriptItem

class TranscriptItem:
role: str # "user" | "assistant"
text: str
timestamp: str | None = None

class SessionEndPayload:
type: Literal["session.end"]
session_id: str
conversation_id: str
agent_id: str | None = None
started_at: str | None = None # ISO timestamp
ended_at: str | None = None # ISO timestamp
duration: int | None = None # Milliseconds
transcription_duration_seconds: float | None = None
tts_duration_seconds: float | None = None
latency: float | None = None
ip_address: str | None = None
country_code: str | None = None
recording_status: str | None = None # "enabled" | "disabled"
transcript: list[TranscriptItem] | None = None
metadata: dict | None = None
from_phone_number: str | None = None
to_phone_number: str | None = None
```

#### `parse_webhook_payload`

Parse raw webhook JSON into the appropriate typed model.

```python
from layercode_create_app.sdk import parse_webhook_payload

payload = parse_webhook_payload({"type": "session.start", ...})
# Returns: SessionStartPayload | MessagePayload | DataPayload | SessionUpdatePayload | SessionEndPayload
```

### `layercode_create_app.sdk.auth`

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "layercode-create-app"
version = "0.0.1"
version = "0.1.0"
description = "Fast scaffolding for LayerCode-ready FastAPI backends"
readme = "README.md"
requires-python = ">=3.12"
Expand Down
2 changes: 1 addition & 1 deletion src/layercode_create_app/agents/bakery.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def __init__(self, model: str) -> None:
self._orders: list[dict[str, Any]] = []
self._reservations: list[dict[str, Any]] = []

prompt = load_prompt(PROMPTS_DIR / "bakery.txt")
prompt = load_prompt(PROMPTS_DIR / "bakery.txt", meta="strict")
system_prompt = str(prompt.prompt)

model_settings = None
Expand Down
32 changes: 29 additions & 3 deletions src/layercode_create_app/agents/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@

from abc import ABC, abstractmethod
from collections.abc import Callable
from typing import TypeVar
from typing import Any, TypeVar

from pydantic_ai.messages import ModelMessage

from ..sdk.events import MessagePayload, SessionEndPayload, SessionStartPayload
from ..sdk.events import (
DataPayload,
MessagePayload,
SessionEndPayload,
SessionStartPayload,
SessionUpdatePayload,
)
from ..sdk.stream import StreamHelper


Expand Down Expand Up @@ -37,7 +43,27 @@ async def handle_message(
"""Handle `message` events and return messages to append to history."""

async def handle_session_end(self, payload: SessionEndPayload) -> None:
"""Handle `session.end` events."""
"""Handle `session.end` events.

Override to perform cleanup, persist transcripts, or log analytics.
"""

return None

async def handle_session_update(self, payload: SessionUpdatePayload) -> None:
"""Handle `session.update` events (e.g., recording completed).

Override to process recording URLs, handle recording failures, etc.
"""

return None

async def handle_data(self, payload: DataPayload) -> dict[str, Any] | None:
"""Handle `data` events (client-sent structured JSON).

Override to process client-side data payloads. Return a dict to send
a JSON response, or None for a default {"status": "ok"} response.
"""

return None

Expand Down
Loading
Loading