> ## Documentation Index
> Fetch the complete documentation index at: https://docs.declaw.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# PTY access through the OpenAI adapter

> Access the full declaw PTY surface from an OpenAI Agents SDK session — send keystrokes, resize, read output via SSE.

## Use case

The OpenAI Agents SDK adapter routes one-shot commands through plain
`exec`, but sometimes an agent needs a real PTY: TUI programs,
progress bars, stateful shell sessions, or anything that calls
`isatty()`. This recipe shows how to reach the full declaw PTY
module from an adapter session so you get the best of both worlds --
the Agents SDK tool loop **and** interactive terminal access when you
need it.

## What you'll learn

* Creating an OpenAI adapter session with `DeclawSandboxClient`
* Reaching the PTY surface via `session._inner._sbx.pty`
* Sending keystrokes, reading output, and resizing with the **async** API
* Running a progress bar that would be garbled through a non-PTY pipe
* Verifying env passthrough and shell state persistence across sends

## Prerequisites

<Snippet file="snippets/env-setup.mdx" />

```bash theme={null}
pip install "declaw[openai-agents]"
```

<Note>
  An `OPENAI_API_KEY` is **not** required for this example. It
  exercises the PTY surface directly without making any LLM calls.
</Note>

## Code walkthrough

### Create the session

```python theme={null}
from declaw import PtySize
from declaw.openai import DeclawSandboxClient, DeclawSandboxClientOptions

client = DeclawSandboxClient()
session = await client.create(options=DeclawSandboxClientOptions(
    template="base",
    timeout=120,
    envs={"PTY_DEMO": "declaw-openai-pty"},
))
```

### Open a PTY through the adapter

The adapter session exposes the underlying `AsyncSandbox` so you can
use the PTY module directly:

```python theme={null}
pty = session._inner._sbx.pty
buf = bytearray()
handle = await pty.create(on_data=lambda c: buf.extend(c), timeout=30)
```

### Four quick probes

```python theme={null}
# 1. Env passthrough
await handle.send_stdin(b"printenv PTY_DEMO\n")

# 2. Shell state persists across sends
await handle.send_stdin(b"X=42; echo X=$X\n")
await handle.send_stdin(b"echo still X=$X\n")

# 3. ANSI progress bar (garbled without a real PTY)
await handle.send_stdin(
    b"for i in $(seq 1 20); do "
    b"printf '\\r[%-20s] %d%%' $(printf '#%.0s' $(seq 1 $i)) $((i*5)); "
    b"sleep 0.05; done; echo\n"
)

# 4. Resize
await handle.resize(PtySize(cols=100, rows=30))
await handle.send_stdin(b"stty size\n")
```

### Clean up

```python theme={null}
await handle.kill()
await client.delete(session=session)
```

## Running it

```bash theme={null}
export DECLAW_API_KEY="your-api-key"
export DECLAW_DOMAIN="api.declaw.ai"
python cookbook/examples/openai-agents-pty/main.py
```

## Expected output

```
pty pid=7

== PTY output ==
declaw-openai-pty
=== state ===
X=42
still X=42
=== progress ===
[####################] 100%
=== size ===
30 100
```

The progress bar renders as a single in-place line because the PTY
supports `\r` cursor return -- a plain exec pipe would have printed
20 separate lines.

## Full source

See `cookbook/examples/openai-agents-pty/main.py` in the repo.
