Skip to content

Latest commit

 

History

History
216 lines (168 loc) · 6.6 KB

File metadata and controls

216 lines (168 loc) · 6.6 KB

Hawk SDK for Python

Official Python client for the Hawk daemon API

Python License CI


The Hawk SDK for Python is the official client for interacting with the Hawk daemon API. It provides an idiomatic Python interface for chat, streaming, sessions, stats, and agent orchestration. Both synchronous and async clients are provided, with the same API surface on each.

Features

  • Dual client pattern - HawkClient (sync) and AsyncHawkClient with identical method signatures
  • Streaming - SSE-based real-time chat streaming via StreamReader / AsyncStreamReader
  • Type safety - Pydantic v2 models with full type hints (Python 3.9+)
  • Context managers - with HawkClient() as client: for automatic resource cleanup
  • Typed errors - Status-code-based exception hierarchy (AuthenticationError, NotFoundError, RateLimitError, etc.)
  • Retry with backoff - Configurable exponential backoff with jitter via RetryConfig
  • Minimal dependencies - httpx (HTTP), pydantic v2, and eval-type-backport (Python < 3.10)

Installation

pip install hawk-sdk

Quick Start

Synchronous

from hawk import HawkClient

with HawkClient() as client:
    # Health check
    health = client.health()
    print(f"Status: {health.status}, Version: {health.version}")
    print(f"Active sessions: {health.active_sessions}")

    # Chat
    response = client.chat("Explain decorators in Python")
    print(response.response)
    print(f"Tokens: in={response.tokens_in}, out={response.tokens_out}")

Asynchronous

import asyncio
from hawk import AsyncHawkClient

async def main():
    async with AsyncHawkClient() as client:
        health = await client.health()
        print(f"Status: {health.status}, Version: {health.version}")

        response = await client.chat("Hello!")
        print(response.response)

asyncio.run(main())

Streaming

from hawk import HawkClient

with HawkClient() as client:
    with client.chat_stream("Write a haiku about coding") as stream:
        for event in stream.events():
            if event.event is None or event.event == "content":
                print(event.data, end="", flush=True)
        print()

    # Or collect all text at once
    with client.chat_stream("Write a haiku about coding") as stream:
        text = stream.collect_text()
        print(f"Full response: {text}")

    # Or collect tool calls
    with client.chat_stream("What tools do I have?", tools=[{"type": "list"}]) as stream:
        calls = stream.collect_tool_calls()
        for call in calls:
            print(f"Tool: {call.name}({call.arguments})")

API Reference

HawkClient / AsyncHawkClient

Both clients share the same constructor and method signatures.

Constructor

HawkClient(
    base_url: str = "http://127.0.0.1:4590",
    api_key: str | None = None,
    retry_config: RetryConfig | None = None,
    timeout: float = 30.0,
    pool_connections: int = 10,
    pool_maxsize: int = 100,
)
Parameter Default Description
base_url http://127.0.0.1:4590 Base URL of the Hawk daemon
api_key None Bearer token for authenticated requests
retry_config DEFAULT_RETRY_CONFIG Retry configuration (429, 5xx, etc.)
timeout 30.0 HTTP request timeout in seconds
pool_connections 10 Max keep-alive connections
pool_maxsize 100 Max total connections

Methods

# Health
health() -> HealthResponse

# Chat
chat(
    prompt: str,
    session_id: str | None = None,
    model: str | None = None,
    max_turns: int | None = None,
    autonomy: str | None = None,
    cwd: str | None = None,
    agent: str | None = None,
    tools: list[dict[str, Any]] | None = None,
    tool_results: list[ToolResult] | None = None,
) -> ChatResponse

# Streaming chat
chat_stream(
    prompt: str,
    session_id: str | None = None,
    model: str | None = None,
    max_turns: int | None = None,
    autonomy: str | None = None,
    cwd: str | None = None,
    agent: str | None = None,
    tools: list[dict[str, Any]] | None = None,
    tool_results: list[ToolResult] | None = None,
) -> StreamReader  # or AsyncStreamReader

# Session management
get_session(session_id: str) -> SessionDetail
list_sessions() -> list[SessionSummary]
delete_session(session_id: str) -> None

# Messages with pagination
list_messages(
    session_id: str,
    limit: int = 50,
    offset: int = 0,
) -> PaginatedResponse[Message]

# Stats
stats() -> StatsResponse

Typed Errors

All API errors inherit from HawkAPIError. Catch specific subclasses:

from hawk import HawkClient, HawkAPIError, RateLimitError, AuthenticationError

with HawkClient() as client:
    try:
        response = client.chat("Hello")
    except AuthenticationError:
        print("Invalid API key")
    except RateLimitError as e:
        print(f"Rate limited, retry after {e.retry_after}s")
    except HawkAPIError as e:
        print(f"API error {e.status_code}: {e.message}")

Error classes: BadRequestError (400), AuthenticationError (401), ForbiddenError (403), NotFoundError (404), RateLimitError (429), InternalServerError (500), ServiceUnavailableError (503).

Error Handling

Every public method wraps its logic in a retry closure that automatically:

  • Respects Retry-After headers on 429 responses
  • Uses exponential backoff with jitter for retryable statuses (429, 500, 502, 503, 504)
  • Raises typed exceptions matching the HTTP status code

Ecosystem Boundaries

hawk-sdk-python is a consumer of Hawk public APIs and contracts. It does not import from engine repos such as eyrie, yaad, tok, trace, sight, or inspect. If a capability is needed across repos, it should be exposed through Hawk or hawk-core-contracts, not through engine internals.

Contributing

Contributions are welcome — please read CONTRIBUTING.md before opening a pull request.

License

MIT - see LICENSE for details.