Files
oh-my-pi/docs/python-dynamic-docs.md
T
can1357 1d414c1383 feat: added IPython-backed Python tool with streaming output and Jupyter integration
- Added IPython-backed Python tool with streaming output and image/JSON rendering.
- Implemented Jupyter kernel gateway integration with WebSocket communication.
- Added Python prelude with 30+ shell-like utility functions for file operations.
- Migrated environment variables from PI_ to OMP_ prefix with automatic migration.
- Added streaming output system with automatic spill-to-disk for large outputs.
- Reorganized settings interface into behavior, tools, display, voice, status, lsp, and exa tabs.
2026-01-18 19:32:27 +01:00

267 lines
7.6 KiB
Markdown

# Dynamic Python Tool Documentation
## Problem
`packages/coding-agent/src/prompts/tools/python.md` contains hardcoded documentation for prelude helpers:
```markdown
### File I/O
- `read(path, limit=None)` — read file, optional char limit
- `write(path, content)` — write file (creates parents)
...
```
This duplicates information already present in `python-prelude.py` (function signatures and docstrings). When the prelude changes, the markdown must be manually updated — they drift.
## Goal
Extract helper documentation from the running Python environment via runtime introspection, then populate `python.md` using Handlebars templating (same system as `system-prompt.md`).
## Current Flow
```
startup
└─> checkPythonKernelAvailability(cwd) // just checks if kernel_gateway is installed
└─> returns { ok: true/false, reason }
createTools(session)
└─> if python available && mode != "bash-only"
└─> createPythonTool(session)
└─> description: renderPromptTemplate(pythonDescription) // no context, static
```
## Proposed Flow
```
startup
└─> warmPythonKernel(cwd)
├─> start kernel gateway + kernel
├─> run prelude
├─> run introspection snippet
│ └─> returns JSON: [{ name, signature, docstring, category }, ...]
├─> cache extracted docs in module-level state
└─> returns { ok, reason, kernel }
createTools(session)
└─> if python available && mode != "bash-only"
└─> createPythonTool(session)
└─> description: renderPromptTemplate(pythonDescription, { helpers: cachedDocs })
session reset
└─> kernel.restart() // reuse same gateway, just restart kernel
```
## Key Changes
### 1. `python-kernel.ts` — Add introspection method
```typescript
interface PreludeHelper {
name: string;
signature: string;
docstring: string;
}
class PythonKernel {
// existing...
async introspectPrelude(): Promise<PreludeHelper[]> {
const result = await this.execute(INTROSPECTION_SNIPPET);
return JSON.parse(result.output);
}
}
```
### 2. `python-executor.ts` — Expose warm kernel + cached docs
```typescript
let cachedPreludeDocs: PreludeHelper[] | null = null;
export async function warmPythonEnvironment(cwd: string): Promise<{
ok: boolean;
reason?: string;
docs: PreludeHelper[];
}> {
// 1. Check availability (existing)
// 2. Start kernel (new - currently deferred to first execute)
// 3. Run prelude (already happens on kernel start)
// 4. Introspect and cache
// 5. Return docs
}
export function getPreludeDocs(): PreludeHelper[] {
return cachedPreludeDocs ?? [];
}
```
### 3. `tools/index.ts` — Warm kernel during tool creation
```typescript
export async function createTools(session: ToolSession, toolNames?: string[]): Promise<Tool[]> {
// ...existing python availability check...
if (shouldCheckPython) {
const warmup = await warmPythonEnvironment(session.cwd);
pythonAvailable = warmup.ok;
// docs now cached for use by createPythonTool
}
// ...rest unchanged...
}
```
### 4. `tools/python.ts` — Pass docs to template
```typescript
import { getPreludeDocs } from "../python-executor";
export function createPythonTool(session: ToolSession): AgentTool<typeof pythonSchema> {
const helpers = getPreludeDocs();
const categories = groupByCategory(helpers); // group into File I/O, Navigation, etc.
return {
name: "python",
description: renderPromptTemplate(pythonDescription, { categories }),
// ...
};
}
```
### 5. `prompts/tools/python.md` — Use Handlebars
```markdown
## Prelude helpers
All helpers auto-print results and return values for chaining.
{{#each categories}}
### {{name}}
{{#each functions}}
- `{{name}}{{signature}}` — {{docstring}}
{{/each}}
{{/each}}
```
## Introspection Snippet
```python
import inspect, json
CATEGORIES = {
'read': 'File I/O', 'write': 'File I/O', 'append': 'File I/O', ...
'cp': 'File operations', 'mv': 'File operations', ...
}
helpers = []
for name, cat in CATEGORIES.items():
obj = globals().get(name)
if not callable(obj):
continue
sig = str(inspect.signature(obj))
doc = (inspect.getdoc(obj) or '').split('\n')[0]
helpers.append({'name': name, 'signature': sig, 'docstring': doc, 'category': cat})
print(json.dumps(helpers))
```
## Category Mapping
Categories are defined in the introspection snippet (Python side), not TypeScript. This keeps the source of truth in one place. Order:
1. File I/O: `read`, `write`, `append`, `touch`, `cat`
2. File operations: `cp`, `mv`, `rm`, `mkdir`
3. Navigation: `pwd`, `cd`, `ls`, `tree`, `stat`
4. Search: `find`, `glob_files`, `grep`, `rgrep`
5. Text processing: `head`, `tail`, `sort_lines`, `uniq`, `cols`, `wc`
6. Find and replace: `replace`, `sed`, `rsed`
7. Batch operations: `batch`, `diff`
8. Shell bridge: `run`, `bash`, `env`
## Session Reset Behavior
Currently: `reset: true` parameter triggers full kernel restart via `restartKernelSession()`.
Proposed: Same behavior, but the pre-warmed kernel is the one being restarted. No change needed — the existing session management already handles this.
## Environment Variable Override
Add `OMP_PY` environment variable to override the settings preference:
| Value | Mode | Description |
|-------|------|-------------|
| `0` or `bash` | bash-only | Disable Python tool, use bash only |
| `1` or `py` | ipy-only | Disable bash tool, use Python only |
| `mix` or `both` | both | Enable both bash and Python tools |
Priority: `OMP_PY` env var > settings preference > default (`ipy-only`)
### Implementation
In `tools/index.ts`:
```typescript
function getPythonModeFromEnv(): "ipy-only" | "bash-only" | "both" | null {
const value = process.env.OMP_PY?.toLowerCase();
if (!value) return null;
switch (value) {
case "0":
case "bash":
return "bash-only";
case "1":
case "py":
return "ipy-only";
case "mix":
case "both":
return "both";
default:
return null;
}
}
export async function createTools(session: ToolSession, toolNames?: string[]): Promise<Tool[]> {
// ...
const pythonMode = getPythonModeFromEnv() ?? session.settings?.getPythonToolMode?.() ?? "ipy-only";
// ...
}
```
### Use Cases
- `OMP_PY=0 omp` — Force bash mode for compatibility testing
- `OMP_PY=1 omp` — Force Python mode even if settings say otherwise
- `OMP_PY=mix omp` — Enable both for users who want choice
## Fallback
If kernel warmup fails (no Python, no kernel_gateway), `getPreludeDocs()` returns empty array. The template should handle this gracefully:
```markdown
{{#if categories.length}}
## Prelude helpers
...
{{else}}
## Prelude helpers
(Documentation unavailable — Python kernel failed to start)
{{/if}}
```
## Files to Modify
1. `packages/coding-agent/src/core/python-kernel.ts` — Add `introspectPrelude()` method
2. `packages/coding-agent/src/core/python-executor.ts` — Add `warmPythonEnvironment()`, `getPreludeDocs()`
3. `packages/coding-agent/src/core/tools/index.ts` — Call warmup during `createTools()`, add `OMP_PY` env override
4. `packages/coding-agent/src/core/tools/python.ts` — Pass docs context to template
5. `packages/coding-agent/src/prompts/tools/python.md` — Convert to Handlebars template
6. `packages/coding-agent/src/core/python-prelude.py` — Add category markers or rely on introspection snippet
## Testing
1. Unit test: `introspectPrelude()` returns expected structure
2. Unit test: `warmPythonEnvironment()` populates cache
3. Unit test: Template renders correctly with mock docs
4. Integration test: Full flow from startup to tool description containing extracted docs
5. Fallback test: Empty docs when Python unavailable