Skip to main content
Status: Actively Maintained - The Python SDK is actively maintained alongside the TypeScript SDK and provides full feature parity.

Chucky Client

The main client class for interacting with Chucky in Python.

Installation

pip install chucky-sdk
Or with Poetry:
poetry add chucky-sdk

Import

from chucky import Chucky

Constructor

client = Chucky(
    token: str,
    url: str = 'wss://conjure.chucky.cloud/ws',
    model: str = 'claude-sonnet-4-5-20250929',
    system_prompt: Optional[str] = None,
    max_turns: Optional[int] = None,
    timeout: float = 30.0,
    keepalive_interval: float = 60.0,
)

Parameters

ParameterTypeRequiredDefaultDescription
tokenstrYes-JWT authentication token
urlstrNo'wss://conjure.chucky.cloud/ws'WebSocket server URL
modelstrNo'claude-sonnet-4-5-20250929'Claude model to use
system_promptstrNoNoneSystem prompt
max_turnsintNoNoneMax conversation turns
timeoutfloatNo30.0Connection timeout (seconds)
keepalive_intervalfloatNo60.0Keep-alive interval (seconds)

Example

from chucky import Chucky

client = Chucky(
    token='your-jwt-token',
    model='claude-sonnet-4-5-20250929',
    system_prompt='You are a helpful assistant.',
    timeout=60.0,
)

Methods

prompt()

Send a one-shot prompt and get the response.
async def prompt(
    message: str,
    *,
    model: Optional[str] = None,
    system_prompt: Optional[str] = None,
    tools: Optional[List[Tool]] = None,
    max_turns: Optional[int] = None,
) -> PromptResult

Parameters

ParameterTypeDescription
messagestrThe prompt message
modelstrOverride default model
system_promptstrOverride default system prompt
toolsList[Tool]Tools available to Claude
max_turnsintMax conversation turns

Returns

PromptResult with the response.

Example

result = await client.prompt('What is the capital of France?')
print(result.result)  # "The capital of France is Paris."
print(result.total_cost_usd)  # 0.0012

stream()

Send a prompt with streaming response.
async def stream(
    message: str,
    *,
    model: Optional[str] = None,
    system_prompt: Optional[str] = None,
    tools: Optional[List[Tool]] = None,
    max_turns: Optional[int] = None,
) -> AsyncGenerator[StreamEvent, None]

Example

async for event in client.stream('Write a poem about coding'):
    if event.type == 'message':
        print(event.data, end='', flush=True)
    elif event.type == 'tool_use':
        print(f'\n[Using tool: {event.name}]')

create_session()

Create a new conversation session. Returns a Session immediately; connection happens on first send().
def create_session(
    *,
    model: Optional[str] = None,
    system_prompt: Optional[str] = None,
    tools: Optional[List[Tool]] = None,
    max_turns: Optional[int] = None,
) -> Session

Example

# create_session is synchronous - returns Session immediately
session = client.create_session(
    model='claude-sonnet-4-5-20250929',
    system_prompt='You are a math tutor.',
)

# Connection happens on first send()
await session.send('What is calculus?')
async for msg in session.stream():
    if msg.get('type') == 'result':
        print(msg.get('result'))

await session.close()

close()

Close the client and release resources.
async def close() -> None

Example

await client.close()

Session Class

The Session class provides a multi-turn conversation interface.

Methods

send()

Send a message to the session. Use stream() to get the response.
async def send(message: str) -> None

stream()

Stream the response after sending a message.
async def stream() -> AsyncIterator[Dict[str, Any]]
Returns an async iterator of SDK messages (dicts with type, message, etc.).

receive()

Alias for stream() for V2 SDK compatibility.
async def receive() -> AsyncIterator[Dict[str, Any]]

close()

Close the session and release resources.
async def close() -> None

Properties

session_id

The session’s unique identifier.
@property
def session_id(self) -> str

Session Example

session = client.create_session(model='claude-sonnet-4-5-20250929')

# First turn
await session.send('Hello, I am Alice')
async for msg in session.stream():
    if msg.get('type') == 'assistant':
        text = get_assistant_text(msg)
        print(text)
    if msg.get('type') == 'result':
        break

# Second turn - maintains conversation context
await session.send('What is my name?')
async for msg in session.stream():
    if msg.get('type') == 'result':
        print(msg.get('result'))  # "Your name is Alice."

await session.close()

Message Types

Messages are dictionaries matching the SDK protocol.

Result Message

{
    'type': 'result',
    'subtype': 'success',  # or 'error'
    'result': str,         # Final response text
    'session_id': str,
    'total_cost_usd': float,
    'duration_ms': int,
    'num_turns': int,
}

Assistant Message

{
    'type': 'assistant',
    'message': {
        'role': 'assistant',
        'content': [
            {'type': 'text', 'text': '...'},
            {'type': 'tool_use', 'id': '...', 'name': '...', 'input': {...}},
        ]
    }
}

Helper Functions

Use these helpers to extract text from messages:
from chucky import get_assistant_text, get_result_text

# Extract text from assistant message
text = get_assistant_text(msg)  # Returns concatenated text blocks

# Extract result from result message
result = get_result_text(msg)  # Returns result string or None

StreamEvent (Legacy)

For the legacy client.stream() API:
@dataclass
class StreamEvent:
    type: str   # 'message', 'result', 'error', 'done'
    data: Any   # The message dict

Complete Example

import asyncio
from chucky import (
    Chucky,
    create_token,
    create_budget,
    tool,
    text_result,
    get_assistant_text,
)

# Create a token (server-side only)
token = create_token(
    user_id='user-123',
    project_id='your-project-id',  # From app.chucky.cloud
    secret='your-hmac-secret',     # From app.chucky.cloud
    budget=create_budget(ai_dollars=1.00, compute_hours=1, window='day'),
)

# Define a tool
@tool('get_time', 'Get current time')
async def get_time() -> dict:
    from datetime import datetime
    return text_result(datetime.now().isoformat())

async def main():
    # Create client
    client = Chucky(
        url='wss://conjure.chucky.cloud/ws',
        token=token,
        model='claude-sonnet-4-5-20250929',
    )

    try:
        # Simple prompt
        result = await client.prompt('What is 2 + 2?')
        print(f"Answer: {result.get('result')}")
        print(f"Cost: ${result.get('total_cost_usd', 0):.4f}")

        # Multi-turn session with tools
        session = client.create_session(tools=[get_time])

        await session.send('What time is it?')
        async for msg in session.stream():
            if msg.get('type') == 'assistant':
                print(get_assistant_text(msg))
            if msg.get('type') == 'result':
                print(f"Done: {msg.get('result')}")
                break

        await session.close()

    finally:
        await client.close()

asyncio.run(main())

Error Handling

Errors are returned as result messages with subtype: 'error':
from chucky import Chucky

client = Chucky(url='wss://conjure.chucky.cloud/ws', token='...')

try:
    result = await client.prompt('Hello')
    if result.get('subtype') == 'error':
        print(f"Error: {result.get('result')}")
    else:
        print(result.get('result'))
except RuntimeError as e:
    # Connection errors, timeouts
    print(f'Connection error: {e}')
except Exception as e:
    print(f'Unexpected error: {e}')
For streaming, check for error messages:
session = client.create_session()
await session.send('Hello')
async for msg in session.stream():
    if msg.get('type') == 'error':
        print(f"Error: {msg.get('error', {}).get('message')}")
        break