codebind 0.1.4__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codebind-0.1.4 → codebind-0.2.0}/PKG-INFO +28 -6
- {codebind-0.1.4 → codebind-0.2.0}/README.md +26 -3
- {codebind-0.1.4 → codebind-0.2.0}/pyproject.toml +2 -3
- {codebind-0.1.4 → codebind-0.2.0}/pyproject.toml.orig +2 -3
- {codebind-0.1.4 → codebind-0.2.0}/src/codebind/__init__.py +3 -0
- codebind-0.2.0/src/codebind/cli.py +17 -0
- codebind-0.2.0/src/codebind/display.py +15 -0
- {codebind-0.1.4 → codebind-0.2.0}/src/codebind/execution.py +31 -9
- codebind-0.2.0/src/codebind/extension.py +34 -0
- {codebind-0.1.4 → codebind-0.2.0}/src/codebind/session.py +47 -8
- codebind-0.1.4/src/codebind/cli.py +0 -45
- codebind-0.1.4/src/codebind/prompts.py +0 -36
- codebind-0.1.4/src/codebind/rendering.py +0 -43
- {codebind-0.1.4 → codebind-0.2.0}/LICENSE +0 -0
- {codebind-0.1.4 → codebind-0.2.0}/src/codebind/py.typed +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codebind
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: A
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A frontend-neutral model loop for persistent IPython sessions.
|
|
5
5
|
Keywords: ai,ipython,llm,repl,agents
|
|
6
6
|
Author: Giovanni Gravili
|
|
7
7
|
Author-email: Giovanni Gravili <ghovax@users.noreply.github.com>
|
|
@@ -17,7 +17,6 @@ Classifier: Typing :: Typed
|
|
|
17
17
|
Requires-Dist: ipython>=9.0
|
|
18
18
|
Requires-Dist: langchain-core>=1.0
|
|
19
19
|
Requires-Dist: models-provider>=0.1.0
|
|
20
|
-
Requires-Dist: rich>=14.0
|
|
21
20
|
Requires-Python: >=3.13
|
|
22
21
|
Project-URL: Repository, https://github.com/ghovax/codebind
|
|
23
22
|
Project-URL: Issues, https://github.com/ghovax/codebind/issues
|
|
@@ -25,7 +24,7 @@ Description-Content-Type: text/markdown
|
|
|
25
24
|
|
|
26
25
|
# Codebind
|
|
27
26
|
|
|
28
|
-
Codebind runs a model with one tool: an IPython cell executed in the session
|
|
27
|
+
Codebind is an IPython extension that runs a model with one tool: an IPython cell executed in the active session. IPython owns execution, namespace, history, magics, tracebacks, and rich display; Codebind owns only conversation and model orchestration.
|
|
29
28
|
|
|
30
29
|
## Installation
|
|
31
30
|
|
|
@@ -41,13 +40,13 @@ Or install it with any Python package installer:
|
|
|
41
40
|
pip install codebind
|
|
42
41
|
```
|
|
43
42
|
|
|
44
|
-
After installation, start the
|
|
43
|
+
After installation, start ordinary IPython with the Codebind extension from any directory:
|
|
45
44
|
|
|
46
45
|
```console
|
|
47
46
|
codebind
|
|
48
47
|
```
|
|
49
48
|
|
|
50
|
-
It opens IPython with `chat` and `Models`
|
|
49
|
+
It opens standard IPython with `chat` and `Models` in the user namespace. All normal IPython command-line options remain available.
|
|
51
50
|
|
|
52
51
|
```python
|
|
53
52
|
models = Models({"openai": "OPENAI_API_KEY"})
|
|
@@ -60,6 +59,29 @@ chat.ask(
|
|
|
60
59
|
|
|
61
60
|
Codebind does not load files or construct a project prompt automatically. The user states what should be loaded as context. Pass `instructions=` to `Session` only when an application needs its own system instructions.
|
|
62
61
|
|
|
62
|
+
## Jupyter
|
|
63
|
+
|
|
64
|
+
Install Codebind in the environment used by a Jupyter kernel, then start the Jupyter frontend normally:
|
|
65
|
+
|
|
66
|
+
```console
|
|
67
|
+
pip install codebind jupyterlab
|
|
68
|
+
jupyter lab
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Load Codebind in a notebook:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
%load_ext codebind
|
|
75
|
+
|
|
76
|
+
models = Models({"openai": "OPENAI_API_KEY"})
|
|
77
|
+
model = models.chat("openai/gpt-5")
|
|
78
|
+
await chat.aask("Inspect the current notebook state.", model)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Codebind publishes cells, assistant Markdown, stdout, tracebacks, and rich results through IPython's MIME display system. The active frontend decides how to render HTML, Markdown, images, SVG, audio, tables, and plain text.
|
|
82
|
+
|
|
83
|
+
Model-authored cells are recorded in native IPython history and displayed through the active frontend. A kernel cannot insert a genuine input cell into every possible frontend without a frontend-specific extension, so Codebind does not attempt to control notebook or editor UI.
|
|
84
|
+
|
|
63
85
|
## ChatGPT account login
|
|
64
86
|
|
|
65
87
|
Models Provider can start its OpenAI browser sign-in flow directly from IPython:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Codebind
|
|
2
2
|
|
|
3
|
-
Codebind runs a model with one tool: an IPython cell executed in the session
|
|
3
|
+
Codebind is an IPython extension that runs a model with one tool: an IPython cell executed in the active session. IPython owns execution, namespace, history, magics, tracebacks, and rich display; Codebind owns only conversation and model orchestration.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -16,13 +16,13 @@ Or install it with any Python package installer:
|
|
|
16
16
|
pip install codebind
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
After installation, start the
|
|
19
|
+
After installation, start ordinary IPython with the Codebind extension from any directory:
|
|
20
20
|
|
|
21
21
|
```console
|
|
22
22
|
codebind
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
It opens IPython with `chat` and `Models`
|
|
25
|
+
It opens standard IPython with `chat` and `Models` in the user namespace. All normal IPython command-line options remain available.
|
|
26
26
|
|
|
27
27
|
```python
|
|
28
28
|
models = Models({"openai": "OPENAI_API_KEY"})
|
|
@@ -35,6 +35,29 @@ chat.ask(
|
|
|
35
35
|
|
|
36
36
|
Codebind does not load files or construct a project prompt automatically. The user states what should be loaded as context. Pass `instructions=` to `Session` only when an application needs its own system instructions.
|
|
37
37
|
|
|
38
|
+
## Jupyter
|
|
39
|
+
|
|
40
|
+
Install Codebind in the environment used by a Jupyter kernel, then start the Jupyter frontend normally:
|
|
41
|
+
|
|
42
|
+
```console
|
|
43
|
+
pip install codebind jupyterlab
|
|
44
|
+
jupyter lab
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Load Codebind in a notebook:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
%load_ext codebind
|
|
51
|
+
|
|
52
|
+
models = Models({"openai": "OPENAI_API_KEY"})
|
|
53
|
+
model = models.chat("openai/gpt-5")
|
|
54
|
+
await chat.aask("Inspect the current notebook state.", model)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Codebind publishes cells, assistant Markdown, stdout, tracebacks, and rich results through IPython's MIME display system. The active frontend decides how to render HTML, Markdown, images, SVG, audio, tables, and plain text.
|
|
58
|
+
|
|
59
|
+
Model-authored cells are recorded in native IPython history and displayed through the active frontend. A kernel cannot insert a genuine input cell into every possible frontend without a frontend-specific extension, so Codebind does not attempt to control notebook or editor UI.
|
|
60
|
+
|
|
38
61
|
## ChatGPT account login
|
|
39
62
|
|
|
40
63
|
Models Provider can start its OpenAI browser sign-in flow directly from IPython:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "codebind"
|
|
3
|
-
version = "0.
|
|
4
|
-
description = "A
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "A frontend-neutral model loop for persistent IPython sessions."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.13"
|
|
7
7
|
license = "MIT"
|
|
@@ -26,7 +26,6 @@ dependencies = [
|
|
|
26
26
|
"ipython>=9.0",
|
|
27
27
|
"langchain-core>=1.0",
|
|
28
28
|
"models-provider>=0.1.0",
|
|
29
|
-
"rich>=14.0",
|
|
30
29
|
]
|
|
31
30
|
|
|
32
31
|
[[project.authors]]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "codebind"
|
|
3
|
-
version = "0.
|
|
4
|
-
description = "A
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "A frontend-neutral model loop for persistent IPython sessions."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
7
7
|
{ name = "Giovanni Gravili", email = "ghovax@users.noreply.github.com" }
|
|
@@ -23,7 +23,6 @@ dependencies = [
|
|
|
23
23
|
"ipython>=9.0",
|
|
24
24
|
"langchain-core>=1.0",
|
|
25
25
|
"models-provider>=0.1.0",
|
|
26
|
-
"rich>=14.0",
|
|
27
26
|
]
|
|
28
27
|
|
|
29
28
|
[project.urls]
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from importlib.metadata import version
|
|
4
4
|
|
|
5
5
|
from .execution import ExecutionReport, IPythonExecutor
|
|
6
|
+
from .extension import load_ipython_extension, unload_ipython_extension
|
|
6
7
|
from .session import Session
|
|
7
8
|
|
|
8
9
|
|
|
@@ -13,4 +14,6 @@ __all__ = [
|
|
|
13
14
|
"IPythonExecutor",
|
|
14
15
|
"Session",
|
|
15
16
|
"__version__",
|
|
17
|
+
"load_ipython_extension",
|
|
18
|
+
"unload_ipython_extension",
|
|
16
19
|
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Standard IPython launcher with the Codebind extension loaded."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from IPython import start_ipython
|
|
8
|
+
|
|
9
|
+
from . import __version__
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main() -> None:
|
|
13
|
+
"""Start ordinary IPython with Codebind loaded as an extension."""
|
|
14
|
+
if sys.argv[1:] == ["--version"]:
|
|
15
|
+
print(f"codebind {__version__}")
|
|
16
|
+
return
|
|
17
|
+
start_ipython(argv=["--ext=codebind", *sys.argv[1:]])
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Frontend-neutral output through IPython's MIME display protocol."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from IPython.display import Code, display
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def display_cell(cell: str) -> None:
|
|
9
|
+
"""Display model-authored cell source in the active frontend."""
|
|
10
|
+
display(Code(cell, language="python"))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def display_assistant(text: str) -> None:
|
|
14
|
+
"""Publish assistant text as plain text and Markdown MIME representations."""
|
|
15
|
+
display({"text/plain": text, "text/markdown": text}, raw=True)
|
|
@@ -8,6 +8,9 @@ from typing import Any
|
|
|
8
8
|
from IPython.core.interactiveshell import InteractiveShell
|
|
9
9
|
from IPython.utils.capture import capture_output
|
|
10
10
|
|
|
11
|
+
from .display import display_cell
|
|
12
|
+
|
|
13
|
+
|
|
11
14
|
@dataclass(frozen=True, slots=True)
|
|
12
15
|
class ExecutionReport:
|
|
13
16
|
"""The model-facing result of one IPython cell."""
|
|
@@ -31,18 +34,37 @@ class IPythonExecutor:
|
|
|
31
34
|
self.shell = shell
|
|
32
35
|
|
|
33
36
|
def execute(self, cell: str) -> ExecutionReport:
|
|
34
|
-
"""Execute a cell
|
|
37
|
+
"""Execute a cell through IPython and replay its native rich output."""
|
|
35
38
|
if not isinstance(cell, str) or not cell.strip():
|
|
36
39
|
raise ValueError("cell must be a non-empty string")
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
display_cell(cell)
|
|
42
|
+
with capture_output() as captured:
|
|
43
|
+
result = self.shell.run_cell(cell, store_history=True)
|
|
44
|
+
captured.show()
|
|
45
|
+
|
|
46
|
+
return self._report(result, captured)
|
|
47
|
+
|
|
48
|
+
async def aexecute(self, cell: str) -> ExecutionReport:
|
|
49
|
+
"""Execute an async-capable cell and replay its native rich output."""
|
|
50
|
+
if not isinstance(cell, str) or not cell.strip():
|
|
51
|
+
raise ValueError("cell must be a non-empty string")
|
|
52
|
+
|
|
53
|
+
display_cell(cell)
|
|
54
|
+
transformed = self.shell.transform_cell(cell)
|
|
55
|
+
with capture_output() as captured:
|
|
56
|
+
result = await self.shell.run_cell_async(
|
|
57
|
+
cell,
|
|
58
|
+
store_history=True,
|
|
59
|
+
transformed_cell=transformed,
|
|
60
|
+
)
|
|
61
|
+
captured.show()
|
|
62
|
+
|
|
63
|
+
return self._report(result, captured)
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def _report(result: Any, captured: Any) -> ExecutionReport:
|
|
67
|
+
"""Build the model-facing text projection of an IPython execution."""
|
|
46
68
|
|
|
47
69
|
displays: list[str] = []
|
|
48
70
|
for output in captured.outputs:
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""IPython extension entry points."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from IPython.core.interactiveshell import InteractiveShell
|
|
8
|
+
from models_provider import Models
|
|
9
|
+
|
|
10
|
+
from .session import Session
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_NAMESPACE_ATTRIBUTE = "_codebind_extension_namespace"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def load_ipython_extension(ipython: InteractiveShell) -> None:
|
|
17
|
+
"""Load Codebind into the active IPython user namespace."""
|
|
18
|
+
previous = getattr(ipython, _NAMESPACE_ATTRIBUTE, None)
|
|
19
|
+
if isinstance(previous, dict):
|
|
20
|
+
ipython.drop_by_id(previous)
|
|
21
|
+
namespace: dict[str, Any] = {
|
|
22
|
+
"chat": Session(shell=ipython),
|
|
23
|
+
"Models": Models,
|
|
24
|
+
}
|
|
25
|
+
ipython.push(namespace)
|
|
26
|
+
setattr(ipython, _NAMESPACE_ATTRIBUTE, namespace)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def unload_ipython_extension(ipython: InteractiveShell) -> None:
|
|
30
|
+
"""Remove names added by Codebind without touching user replacements."""
|
|
31
|
+
namespace = getattr(ipython, _NAMESPACE_ATTRIBUTE, None)
|
|
32
|
+
if isinstance(namespace, dict):
|
|
33
|
+
ipython.drop_by_id(namespace)
|
|
34
|
+
delattr(ipython, _NAMESPACE_ATTRIBUTE)
|
|
@@ -12,9 +12,8 @@ from langchain_core.language_models import BaseChatModel
|
|
|
12
12
|
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage, ToolMessage
|
|
13
13
|
from langchain_core.runnables import Runnable
|
|
14
14
|
|
|
15
|
+
from .display import display_assistant
|
|
15
16
|
from .execution import ExecutionReport, IPythonExecutor
|
|
16
|
-
from .prompts import render_cell
|
|
17
|
-
from .rendering import TerminalRenderer
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
IPYTHON_TOOL = {
|
|
@@ -73,7 +72,6 @@ class Session:
|
|
|
73
72
|
|
|
74
73
|
self.shell = resolved_shell
|
|
75
74
|
self.executor = IPythonExecutor(resolved_shell)
|
|
76
|
-
self.renderer = TerminalRenderer()
|
|
77
75
|
self.instructions = instructions.strip() if instructions else None
|
|
78
76
|
self.messages: list[BaseMessage] = []
|
|
79
77
|
self.last_response: AIMessage | None = None
|
|
@@ -106,7 +104,7 @@ class Session:
|
|
|
106
104
|
if not response.tool_calls:
|
|
107
105
|
answer = _message_text(response)
|
|
108
106
|
if answer:
|
|
109
|
-
|
|
107
|
+
display_assistant(answer)
|
|
110
108
|
return
|
|
111
109
|
|
|
112
110
|
for call in response.tool_calls:
|
|
@@ -119,6 +117,38 @@ class Session:
|
|
|
119
117
|
)
|
|
120
118
|
)
|
|
121
119
|
|
|
120
|
+
async def aask(self, prompt: str, model: BaseChatModel) -> None:
|
|
121
|
+
"""Run one user turn asynchronously for notebook and event-loop frontends."""
|
|
122
|
+
text = prompt.strip()
|
|
123
|
+
if not text:
|
|
124
|
+
raise ValueError("prompt cannot be empty")
|
|
125
|
+
|
|
126
|
+
bound_model = self._bind(model)
|
|
127
|
+
self.messages.append(HumanMessage(text))
|
|
128
|
+
|
|
129
|
+
while True:
|
|
130
|
+
response = await bound_model.ainvoke(tuple(self.messages))
|
|
131
|
+
if not isinstance(response, AIMessage):
|
|
132
|
+
raise TypeError("model must return an AIMessage")
|
|
133
|
+
self.messages.append(response)
|
|
134
|
+
self.last_response = response
|
|
135
|
+
|
|
136
|
+
if not response.tool_calls:
|
|
137
|
+
answer = _message_text(response)
|
|
138
|
+
if answer:
|
|
139
|
+
display_assistant(answer)
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
for call in response.tool_calls:
|
|
143
|
+
report = await self._aexecute_call(call)
|
|
144
|
+
identifier = str(call.get("id") or f"ipython-{len(self.messages)}")
|
|
145
|
+
self.messages.append(
|
|
146
|
+
ToolMessage(
|
|
147
|
+
json.dumps(report.as_dict(), ensure_ascii=False),
|
|
148
|
+
tool_call_id=identifier,
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
|
|
122
152
|
@staticmethod
|
|
123
153
|
def _bind(model: BaseChatModel) -> Runnable[Any, BaseMessage]:
|
|
124
154
|
try:
|
|
@@ -133,11 +163,20 @@ class Session:
|
|
|
133
163
|
if not isinstance(arguments, Mapping) or not isinstance(arguments.get("cell"), str):
|
|
134
164
|
return _tool_error("InvalidArguments", "ipython requires a string cell argument")
|
|
135
165
|
|
|
136
|
-
cell = arguments["cell"]
|
|
137
|
-
render_cell(self.shell, cell)
|
|
138
166
|
try:
|
|
139
|
-
report = self.executor.execute(cell)
|
|
167
|
+
report = self.executor.execute(arguments["cell"])
|
|
140
168
|
except Exception as error: # The failure must be returned to the model, not end the session.
|
|
141
169
|
report = _tool_error(type(error).__name__, str(error))
|
|
142
|
-
self.renderer.tool_output(report, self.shell)
|
|
143
170
|
return report
|
|
171
|
+
|
|
172
|
+
async def _aexecute_call(self, call: Mapping[str, Any]) -> ExecutionReport:
|
|
173
|
+
if call.get("name") != "ipython":
|
|
174
|
+
return _tool_error("UnknownTool", f"unknown tool: {call.get('name')!r}")
|
|
175
|
+
arguments = call.get("args")
|
|
176
|
+
if not isinstance(arguments, Mapping) or not isinstance(arguments.get("cell"), str):
|
|
177
|
+
return _tool_error("InvalidArguments", "ipython requires a string cell argument")
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
return await self.executor.aexecute(arguments["cell"])
|
|
181
|
+
except Exception as error:
|
|
182
|
+
return _tool_error(type(error).__name__, str(error))
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
"""Project-local IPython launcher with Codebind conveniences preloaded."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import argparse
|
|
6
|
-
from typing import Any
|
|
7
|
-
|
|
8
|
-
from IPython.core.interactiveshell import InteractiveShell
|
|
9
|
-
from IPython.terminal.ipapp import TerminalIPythonApp
|
|
10
|
-
from models_provider import Models
|
|
11
|
-
from rich.console import Console
|
|
12
|
-
from rich.markdown import Markdown
|
|
13
|
-
|
|
14
|
-
from . import __version__
|
|
15
|
-
from .session import Session
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
BANNER = """\
|
|
19
|
-
- `models = Models({...})`
|
|
20
|
-
- `chat.ask("...", models.chat("provider/model"))`
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def namespace(shell: InteractiveShell) -> dict[str, Any]:
|
|
25
|
-
"""Build the small namespace exposed by the Codebind shell."""
|
|
26
|
-
return {
|
|
27
|
-
"chat": Session(shell=shell),
|
|
28
|
-
"Models": Models,
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def main() -> None:
|
|
33
|
-
"""Start IPython in the current directory with Codebind preloaded."""
|
|
34
|
-
parser = argparse.ArgumentParser(description="Start a model-enabled IPython session.")
|
|
35
|
-
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
36
|
-
parser.parse_args()
|
|
37
|
-
|
|
38
|
-
Console().print(Markdown(BANNER))
|
|
39
|
-
application = TerminalIPythonApp.instance()
|
|
40
|
-
application.display_banner = False
|
|
41
|
-
application.initialize([])
|
|
42
|
-
shell = application.shell
|
|
43
|
-
shell.enable_tip = False
|
|
44
|
-
shell.push(namespace(shell))
|
|
45
|
-
application.start()
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
"""Native IPython prompt rendering for model-authored cells."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import sys
|
|
6
|
-
|
|
7
|
-
from IPython.terminal.ptutils import IPythonPTLexer
|
|
8
|
-
from prompt_toolkit.document import Document
|
|
9
|
-
from prompt_toolkit.formatted_text import FormattedText, PygmentsTokens
|
|
10
|
-
from prompt_toolkit.shortcuts import print_formatted_text
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def render_cell(shell: object, cell: str) -> None:
|
|
14
|
-
"""Render a cell with IPython's lexer, prompt tokens, and terminal style."""
|
|
15
|
-
lexer = IPythonPTLexer().lex_document(Document(cell))
|
|
16
|
-
pt_app = getattr(shell, "pt_app", None)
|
|
17
|
-
style = pt_app.app.style if pt_app is not None else None
|
|
18
|
-
sys.stdout.write(getattr(shell, "separate_in", "\n"))
|
|
19
|
-
prompts = shell.prompts
|
|
20
|
-
|
|
21
|
-
for index, _line in enumerate(cell.split("\n")):
|
|
22
|
-
prompt_tokens = (
|
|
23
|
-
prompts.in_prompt_tokens()
|
|
24
|
-
if index == 0
|
|
25
|
-
else prompts.continuation_prompt_tokens(lineno=index - 1)
|
|
26
|
-
)
|
|
27
|
-
print_formatted_text(PygmentsTokens(prompt_tokens), style=style, end="")
|
|
28
|
-
print_formatted_text(FormattedText(lexer(index)), style=style)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def render_output_prompt(shell: object) -> None:
|
|
32
|
-
"""Render an output prompt with IPython's terminal style."""
|
|
33
|
-
pt_app = getattr(shell, "pt_app", None)
|
|
34
|
-
style = pt_app.app.style if pt_app is not None else None
|
|
35
|
-
sys.stdout.write(getattr(shell, "separate_out", "") or "\n")
|
|
36
|
-
print_formatted_text(PygmentsTokens(shell.prompts.out_prompt_tokens()), style=style, end="")
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
"""Terminal rendering for model responses and IPython tool output."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from rich.console import Console
|
|
6
|
-
from rich.markdown import Markdown
|
|
7
|
-
from rich.text import Text
|
|
8
|
-
|
|
9
|
-
from .execution import ExecutionReport
|
|
10
|
-
from .prompts import render_output_prompt
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def _visible_output(report: ExecutionReport) -> str:
|
|
14
|
-
parts = [part for part in (report.stdout, report.stderr) if part]
|
|
15
|
-
if report.displays:
|
|
16
|
-
parts.extend(report.displays)
|
|
17
|
-
elif not parts and report.result is not None:
|
|
18
|
-
parts.append(report.result)
|
|
19
|
-
if not parts and report.error is not None:
|
|
20
|
-
parts.append(f"{report.error['type']}: {report.error['message']}")
|
|
21
|
-
return "\n".join(part.rstrip("\n") for part in parts if part).strip("\n")
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class TerminalRenderer:
|
|
25
|
-
"""Render tool output and Markdown answers to the active terminal."""
|
|
26
|
-
|
|
27
|
-
def __init__(self) -> None:
|
|
28
|
-
self.console = Console()
|
|
29
|
-
|
|
30
|
-
def tool_output(self, report: ExecutionReport, shell: object) -> None:
|
|
31
|
-
"""Show the complete tool output."""
|
|
32
|
-
visible = _visible_output(report)
|
|
33
|
-
if not visible:
|
|
34
|
-
return
|
|
35
|
-
output = Text.from_ansi(visible)
|
|
36
|
-
if report.ok:
|
|
37
|
-
render_output_prompt(shell)
|
|
38
|
-
self.console.print(output)
|
|
39
|
-
|
|
40
|
-
def assistant(self, text: str) -> None:
|
|
41
|
-
"""Render the final model response as terminal Markdown."""
|
|
42
|
-
self.console.print()
|
|
43
|
-
self.console.print(Markdown(text))
|
|
File without changes
|
|
File without changes
|