A terminal IPython extension that adds Claude Agent SDK powered prompting
Editable install:
pip install -e ipyai
Run tests:
cd ipyai
./tools/test.sh
Capture fresh Claude SDK shape samples:
cd ipyai
./tools/capture_samples.sh
The wrappers intentionally keep setup small:
tools/test.sh sets XDG_CONFIG_HOME and CLAUDE_CONFIG_DIR to repo-local temp dirs, then runs pytesttools/capture_samples.sh regenerates the committed Claude SDK stream-shape artifacts under samples/outputs/lisette; this is the explicit exception to the common canonical-event formatter path and still uses lisette’s native formatterToolRegistry, schema generation, and local tool calling helpersipyai console entry pointipyai uses ipythonng.cli.parse_flags() to split CLI args into ipyai flags and IPython args. parse_flags scans sys.argv[1:] for short flags (e.g. -b, -r, -l) that are not IPython’s own short flags, collects them and their values into IPYTHONNG_FLAGS env var, and passes the rest through to IPython. When the ipyai extension loads, _parse_ng_flags() in core.py reads IPYTHONNG_FLAGS and parses it with argparse. This two-stage approach lets ipyai flags coexist with IPython flags on the same command line (e.g. ipyai -b codex -r 5 --pdb).
. is rewritten into %ipyai.IPyAIExtension.run_prompt() reconstructs recent code/output/note context from IPython history.$nameand shell refs like `!`cmd are injected above the prompt.core.py first builds a typed ConversationSeedprepare_turn(...)s using that seedastream_to_stdout() renders the response through Rich in TTY mode and stores the final transcript text locally.Completion policy is shared in BaseBackend.complete():
ConversationSeedprovider_session_id=Nonetool_mode="off"ephemeral=Truethink="l"Backends can still override complete() if a provider genuinely requires it, but the default path is now the contract.
There are two layers of state:
ipyai uses:
claude_prompts for AI prompt historysessions.remark JSON for cwd, backend, and provider_session_idIf prompt history exists locally but provider_session_id is missing, provider bootstrap is backend-specific:
Notebook save/load is explicit only:
%ipyai save <filename>%ipyai load <filename>ipyai -l <filename>There is no implicit startup.ipynb behavior.
The custom tool story is intentionally small:
pyrun, bash, start_bgterm, write_stdin, close_bgterm, lnhashview_file, exhash_fileBash, Edit, Read, Skill, WebFetch, WebSearch, Writepyrun 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:
mcp__ipy__... namesdynamicToolsThe 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 are Claude-native:
Skill tool is enabledsetting_sources=["user", "project"].claude/plugins up the cwd parent chainThe samples/ directory exists so stream-shape spelunking does not need to be repeated.
Artifacts currently committed:
samples/outputs/text_stream.jsonsamples/outputs/python_tool_stream.jsonsamples/toolslm_sdk_tool_demo.pyThose captures are useful when working on:
StreamEvent shape changesSystemMessage.init payload changessamples/toolslm_sdk_tool_demo.py is a minimal reference for the toolslm.get_schema_nm(...) -> claude_agent_sdk.tool(...) -> create_sdk_mcp_server(...) path.
The test suite is intentionally small and integration-heavy.
Current coverage focuses on:
ipyai resolves config paths via XDG.XDG_CONFIG_HOME so config writes stay out of a normal user config tree.samples/.claude/ directory for Claude session artifacts.