ipyai

A terminal IPython extension that adds Claude and Codex powered prompting

View the Project on GitHub AnswerDotAI/ipyai

DEV

Setup

Editable install:

pip install -e ipyai

Run tests:

cd ipyai
./tools/test.sh

The test harness keeps setup small: tools/test.sh redirects XDG_CONFIG_HOME to a repo-local temp dir so ipyai config writes stay out of a normal user config tree, then runs pytest. It does not redirect CLAUDE_CONFIG_DIR, because on macOS claude -p OAuth reads credentials from the login keychain keyed by the userID in ~/.claude.json; redirecting the config dir breaks that lookup. Instead, ClaudeBackend sweeps any session jsonls it can identify as its own (or as known claude side-effect stubs) after each turn. The live Codex app-server test archives the threads it creates after the test so they do not show up in codex resume.

File Map

CLI Flag Plumbing

ipyai is a ZMQTerminalIPythonApp subclass. IPyAIApp defines the ipyai-specific flags directly with traitlets (-b, -r, -l, -p, --resume-pick, --keep-alive) while still inheriting standard jupyter_console flags. Bare -r is preprocessed to --resume-pick; -r N resumes the concrete ipyai session id.

Current Architecture

Prompt Flow

  1. Input starting with . is rewritten into %ipyai.
  2. IPyAIController.run_prompt() reconstructs recent code/output/note context from IPython history.
  3. Variable refs like $nameand shell refs like `!`cmd are injected above the prompt.
  4. The selected backend streams the turn:
    • core.py first builds a typed ConversationSeed
    • each backend then prepare_turn(...)s using that seed
    • Claude CLI writes a synthetic session JSONL per turn, spawns claude -p --resume, and starts a unix-socket MCP bridge for custom tools
    • Claude API and Codex API both rebuild flat history from the typed seed through the shared _LisetteBackend
    • Codex resumes or bootstraps an app-server thread from the typed seed
  5. astream_to_stdout() renders the response through Rich in TTY mode and stores the final transcript text locally.

Completion policy is shared in BaseBackend.complete():

Backends can still override complete() if a provider genuinely requires it, but the default path is now the contract.

State Model

There are two layers of state:

ipyai uses:

If prompt history exists locally but provider_session_id is missing, provider bootstrap is backend-specific:

Notebook save/load is explicit only:

There is no implicit startup.ipynb behavior.

Tools

The custom tool story is intentionally small:

pyrun does not call back into InteractiveShell.run_cell*. It delegates to safepyrun, looked up in shell.user_ns, matching the old ipycodex direct-call boundary and avoiding nested IPython cell execution.

Provider-specific tool exposure now fans out from the shared ToolRegistry:

The ipyai CLI loads safepyrun before ipyai, so normal terminal sessions get pyrun automatically. ipyai seeds the other custom tools into shell.user_ns directly.

Skills

Skills are Claude-native:

Samples

The samples/ directory holds committed stream-shape artifacts so event-wiring spelunking does not need to be repeated. The capture scripts there still import claude_agent_sdk and are kept only as historical reference; the live Claude backend no longer uses the SDK. To re-capture against claude -p, run it directly with --output-format=stream-json --include-partial-messages --verbose and save the output.

Tests

The test suite is intentionally small and integration-heavy.

Current coverage focuses on:

Notes