codebind 0.6.16__tar.gz → 0.6.18__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.6.16 → codebind-0.6.18}/PKG-INFO +4 -2
- {codebind-0.6.16 → codebind-0.6.18}/README.md +3 -1
- {codebind-0.6.16 → codebind-0.6.18}/pyproject.toml +1 -1
- {codebind-0.6.16 → codebind-0.6.18}/pyproject.toml.orig +1 -1
- codebind-0.6.18/src/codebind/async_runtime.py +20 -0
- {codebind-0.6.16 → codebind-0.6.18}/src/codebind/conversation.py +21 -2
- {codebind-0.6.16 → codebind-0.6.18}/src/codebind/execution.py +15 -6
- {codebind-0.6.16 → codebind-0.6.18}/src/codebind/extension.py +3 -2
- codebind-0.6.18/src/codebind/model_text.py +16 -0
- {codebind-0.6.16 → codebind-0.6.18}/src/codebind/session.py +12 -8
- {codebind-0.6.16 → codebind-0.6.18}/src/codebind/terminal.py +30 -2
- {codebind-0.6.16 → codebind-0.6.18}/LICENSE +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/data/share/jupyter/labextensions/codebind-jupyterlab/install.json +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/data/share/jupyter/labextensions/codebind-jupyterlab/package.json +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/data/share/jupyter/labextensions/codebind-jupyterlab/static/590.98c719a309e23852.js +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/data/share/jupyter/labextensions/codebind-jupyterlab/static/remoteEntry.24258c9500a0850a.js +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/data/share/jupyter/labextensions/codebind-jupyterlab/static/style.js +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/data/share/jupyter/labextensions/codebind-jupyterlab/static/third-party-licenses.json +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/jupyterlab/.yarnrc.yml +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/jupyterlab/install.json +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/jupyterlab/package.json +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/jupyterlab/src/index.ts +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/jupyterlab/tsconfig.json +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/jupyterlab/yarn.lock +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/src/codebind/__init__.py +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/src/codebind/cli.py +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/src/codebind/configuration.py +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/src/codebind/context.md +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/src/codebind/context.py +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/src/codebind/display.py +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/src/codebind/images.py +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/src/codebind/instructions.md +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/src/codebind/jupyter.py +0 -0
- {codebind-0.6.16 → codebind-0.6.18}/src/codebind/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codebind
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.18
|
|
4
4
|
Summary: A frontend-neutral model loop for persistent IPython sessions.
|
|
5
5
|
Keywords: ai,ipython,llm,repl,agents
|
|
6
6
|
Author: Giovanni Gravili
|
|
@@ -77,8 +77,10 @@ The `%question` and `%%question` magics remain available:
|
|
|
77
77
|
Inspect this project and tell me what to implement first.
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
+
Codebind keeps its terminal prompt and internal bookkeeping usable when a tool cell leaves an asyncio loop active on the terminal thread. This also supports sync clients that manage their own loop across tool cells, without changing IPython’s normal top-level `await` behavior.
|
|
81
|
+
|
|
80
82
|
Terminal conversations remain live for the current IPython process. Previous terminal inputs and outputs, including Python cells you ran yourself, enter the model's context when you ask a question. Durable automatic resumption belongs to notebooks, where the notebook file provides an unambiguous conversation identity.
|
|
81
|
-
Model-executed cells appear with IPython's native input counter, syntax highlighting, output prompts, streams, and tracebacks. Assistant Markdown appears before the following tool cell, and tool output appears as it is written. To keep terminal scrollback usable, Codebind previews at most three gray output lines, shortening each line after 80 characters with `...`. Further lines appear as `… N more output lines.`
|
|
83
|
+
Model-executed cells appear with IPython's native input counter, syntax highlighting, output prompts, streams, and tracebacks. Assistant Markdown appears before the following tool cell, and tool output appears as it is written. To keep terminal scrollback usable, Codebind previews at most three gray output lines, shortening each line after 80 characters with `...`. Further lines appear as `… N more output lines.` Full output remains in IPython history, while model-facing text is bounded and displayed images stay available as image blocks; `%output 8` opens the full text of cell 8 in IPython's pager. User-entered Python cells keep IPython's normal display behavior. Assistant Markdown is rendered through IPython's terminal MIME renderer. A terminal is a stream rather than a notebook document, so these displays are not editable notebook cells.
|
|
82
84
|
|
|
83
85
|
## JupyterLab
|
|
84
86
|
|
|
@@ -48,8 +48,10 @@ The `%question` and `%%question` magics remain available:
|
|
|
48
48
|
Inspect this project and tell me what to implement first.
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
Codebind keeps its terminal prompt and internal bookkeeping usable when a tool cell leaves an asyncio loop active on the terminal thread. This also supports sync clients that manage their own loop across tool cells, without changing IPython’s normal top-level `await` behavior.
|
|
52
|
+
|
|
51
53
|
Terminal conversations remain live for the current IPython process. Previous terminal inputs and outputs, including Python cells you ran yourself, enter the model's context when you ask a question. Durable automatic resumption belongs to notebooks, where the notebook file provides an unambiguous conversation identity.
|
|
52
|
-
Model-executed cells appear with IPython's native input counter, syntax highlighting, output prompts, streams, and tracebacks. Assistant Markdown appears before the following tool cell, and tool output appears as it is written. To keep terminal scrollback usable, Codebind previews at most three gray output lines, shortening each line after 80 characters with `...`. Further lines appear as `… N more output lines.`
|
|
54
|
+
Model-executed cells appear with IPython's native input counter, syntax highlighting, output prompts, streams, and tracebacks. Assistant Markdown appears before the following tool cell, and tool output appears as it is written. To keep terminal scrollback usable, Codebind previews at most three gray output lines, shortening each line after 80 characters with `...`. Further lines appear as `… N more output lines.` Full output remains in IPython history, while model-facing text is bounded and displayed images stay available as image blocks; `%output 8` opens the full text of cell 8 in IPython's pager. User-entered Python cells keep IPython's normal display behavior. Assistant Markdown is rendered through IPython's terminal MIME renderer. A terminal is a stream rather than a notebook document, so these displays are not editable notebook cells.
|
|
53
55
|
|
|
54
56
|
## JupyterLab
|
|
55
57
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Bridge synchronous Codebind operations around foreign event loops."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from collections.abc import Awaitable
|
|
7
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
8
|
+
from typing import TypeVar
|
|
9
|
+
|
|
10
|
+
_T = TypeVar("_T")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def run_sync(operation: Awaitable[_T]) -> _T:
|
|
14
|
+
"""Run a coroutine without nesting asyncio.run in a loop owned by another API."""
|
|
15
|
+
try:
|
|
16
|
+
asyncio.get_running_loop()
|
|
17
|
+
except RuntimeError:
|
|
18
|
+
return asyncio.run(operation)
|
|
19
|
+
with ThreadPoolExecutor(max_workers=1, thread_name_prefix="codebind-async") as worker:
|
|
20
|
+
return worker.submit(asyncio.run, operation).result()
|
|
@@ -19,6 +19,7 @@ from langchain_core.messages import (
|
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
from .images import PreparedImage, prepare_mime_image
|
|
22
|
+
from .model_text import bounded_text
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class ConversationStore(Protocol):
|
|
@@ -319,7 +320,19 @@ def _canonical_mime_bundle(
|
|
|
319
320
|
label: str,
|
|
320
321
|
images: list[tuple[str, PreparedImage]] | None,
|
|
321
322
|
) -> dict[str, Any]:
|
|
322
|
-
|
|
323
|
+
def bounded_item(key: str, item: Any) -> Any:
|
|
324
|
+
if key.startswith("text/"):
|
|
325
|
+
if isinstance(item, str):
|
|
326
|
+
return bounded_text(item)
|
|
327
|
+
if isinstance(item, list) and all(isinstance(part, str) for part in item):
|
|
328
|
+
return bounded_text("".join(item))
|
|
329
|
+
return deepcopy(item)
|
|
330
|
+
|
|
331
|
+
data = {
|
|
332
|
+
key: bounded_item(key, item)
|
|
333
|
+
for key, item in value.items()
|
|
334
|
+
if not key.startswith("image/")
|
|
335
|
+
}
|
|
323
336
|
try:
|
|
324
337
|
image = prepare_mime_image(value)
|
|
325
338
|
except ValueError as error:
|
|
@@ -370,7 +383,7 @@ def _normalize_notebook(
|
|
|
370
383
|
):
|
|
371
384
|
raise ValueError("Invalid Codebind notebook cell")
|
|
372
385
|
identifiers.add(identifier)
|
|
373
|
-
cell: dict[str, Any] = {"id": identifier, "type": cell_type, "source": source}
|
|
386
|
+
cell: dict[str, Any] = {"id": identifier, "type": cell_type, "source": bounded_text(source)}
|
|
374
387
|
if cell_type == "code":
|
|
375
388
|
execution_count = raw_cell.get("execution_count")
|
|
376
389
|
outputs = raw_cell.get("outputs", [])
|
|
@@ -384,6 +397,12 @@ def _normalize_notebook(
|
|
|
384
397
|
if not isinstance(output, dict):
|
|
385
398
|
raise ValueError("Invalid Codebind cell output")
|
|
386
399
|
normalized = deepcopy(output)
|
|
400
|
+
if output.get("output_type") == "stream":
|
|
401
|
+
text = output.get("text")
|
|
402
|
+
if isinstance(text, str):
|
|
403
|
+
normalized["text"] = bounded_text(text)
|
|
404
|
+
elif isinstance(text, list) and all(isinstance(part, str) for part in text):
|
|
405
|
+
normalized["text"] = bounded_text("".join(text))
|
|
387
406
|
data = output.get("data")
|
|
388
407
|
if isinstance(data, dict):
|
|
389
408
|
normalized["data"] = _canonical_mime_bundle(
|
|
@@ -16,6 +16,7 @@ from IPython.utils.capture import capture_output
|
|
|
16
16
|
|
|
17
17
|
from .display import display_cell
|
|
18
18
|
from .images import PreparedImage, prepare_mime_image
|
|
19
|
+
from .model_text import bounded_text
|
|
19
20
|
from .jupyter import JupyterLabBridge
|
|
20
21
|
from .terminal import preview_tool_output, record_tool_output, show_input
|
|
21
22
|
|
|
@@ -388,9 +389,10 @@ class IPythonExecutor:
|
|
|
388
389
|
displays: list[str] = []
|
|
389
390
|
images: list[PreparedImage] = []
|
|
390
391
|
omissions: list[str] = []
|
|
392
|
+
seen_images: set[str] = set()
|
|
391
393
|
for index, data in enumerate(bundles):
|
|
392
394
|
if isinstance(data, dict) and "text/plain" in data:
|
|
393
|
-
displays.append(str(data["text/plain"]))
|
|
395
|
+
displays.append(bounded_text(str(data["text/plain"])))
|
|
394
396
|
if not isinstance(data, dict):
|
|
395
397
|
continue
|
|
396
398
|
if len(images) >= _MAXIMUM_IMAGES_PER_REPORT:
|
|
@@ -402,20 +404,27 @@ class IPythonExecutor:
|
|
|
402
404
|
except ValueError as error:
|
|
403
405
|
omissions.append(f"Display {index + 1}: {error}")
|
|
404
406
|
else:
|
|
405
|
-
if image is not None:
|
|
407
|
+
if image is not None and image.source_sha256 not in seen_images:
|
|
406
408
|
images.append(image)
|
|
409
|
+
seen_images.add(image.source_sha256)
|
|
407
410
|
|
|
408
411
|
exception = result.error_before_exec or result.error_in_exec
|
|
409
412
|
error = (
|
|
410
|
-
{"type": type(exception).__name__, "message": str(exception)}
|
|
413
|
+
{"type": type(exception).__name__, "message": bounded_text(str(exception))}
|
|
411
414
|
if exception is not None
|
|
412
415
|
else None
|
|
413
416
|
)
|
|
417
|
+
value = result.result
|
|
418
|
+
rendered_result = (
|
|
419
|
+
f"<{type(value).__name__}: {len(value):,} bytes>"
|
|
420
|
+
if isinstance(value, (bytes, bytearray, memoryview))
|
|
421
|
+
else bounded_text(repr(value)) if value is not None else None
|
|
422
|
+
)
|
|
414
423
|
return ExecutionReport(
|
|
415
424
|
ok=result.success,
|
|
416
|
-
stdout=stdout,
|
|
417
|
-
stderr=stderr,
|
|
418
|
-
result=
|
|
425
|
+
stdout=bounded_text(stdout),
|
|
426
|
+
stderr=bounded_text(stderr),
|
|
427
|
+
result=rendered_result,
|
|
419
428
|
displays=tuple(displays),
|
|
420
429
|
error=error,
|
|
421
430
|
images=tuple(images),
|
|
@@ -12,6 +12,7 @@ from IPython.core.page import page
|
|
|
12
12
|
from IPython.terminal.interactiveshell import TerminalInteractiveShell
|
|
13
13
|
from langchain_core.language_models import BaseChatModel
|
|
14
14
|
|
|
15
|
+
from .async_runtime import run_sync
|
|
15
16
|
from .configuration import load_configuration, load_models
|
|
16
17
|
from .context import format_context
|
|
17
18
|
from .display import display_assistant
|
|
@@ -58,7 +59,7 @@ def _close_state(state: dict[str, Any]) -> None:
|
|
|
58
59
|
try:
|
|
59
60
|
asyncio.get_running_loop().create_task(discard_model())
|
|
60
61
|
except RuntimeError:
|
|
61
|
-
|
|
62
|
+
run_sync(discard_model())
|
|
62
63
|
|
|
63
64
|
|
|
64
65
|
def load_ipython_extension(ipython: InteractiveShell) -> None:
|
|
@@ -263,7 +264,7 @@ def load_ipython_extension(ipython: InteractiveShell) -> None:
|
|
|
263
264
|
try:
|
|
264
265
|
session.send(cell if cell is not None else line, selected, notebook=history)
|
|
265
266
|
except BaseException:
|
|
266
|
-
|
|
267
|
+
run_sync(discard_model())
|
|
267
268
|
raise
|
|
268
269
|
|
|
269
270
|
ipython.register_magic_function(question_magic, "line_cell", "question")
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Keep model-facing text bounded without changing native IPython history."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
MAXIMUM_MODEL_TEXT_CHARACTERS = 16_384
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def bounded_text(value: str, limit: int = MAXIMUM_MODEL_TEXT_CHARACTERS) -> str:
|
|
9
|
+
"""Keep useful head and tail text; do not send enormous binary representations."""
|
|
10
|
+
if len(value) <= limit:
|
|
11
|
+
return value
|
|
12
|
+
if value.startswith(("b'", 'b"', "bytearray(b'", 'bytearray(b"')):
|
|
13
|
+
return f"<binary representation omitted: {len(value):,} characters>"
|
|
14
|
+
head, tail = limit // 2, limit // 4
|
|
15
|
+
omitted = len(value) - head - tail
|
|
16
|
+
return value[:head] + f"\n… {omitted:,} characters omitted …\n" + value[-tail:]
|
|
@@ -27,6 +27,7 @@ from langchain_core.messages.ai import add_ai_message_chunks
|
|
|
27
27
|
from langchain_core.runnables import Runnable
|
|
28
28
|
from models_provider import TransientProviderError
|
|
29
29
|
|
|
30
|
+
from .async_runtime import run_sync
|
|
30
31
|
from .display import display_assistant
|
|
31
32
|
from .conversation import Conversation, ConversationStore, MemoryConversationStore
|
|
32
33
|
from .execution import ExecutionReport, IPythonExecutor
|
|
@@ -146,7 +147,7 @@ class Session:
|
|
|
146
147
|
await self._ensure_loaded()
|
|
147
148
|
|
|
148
149
|
def _ensure_loaded_sync(self) -> None:
|
|
149
|
-
|
|
150
|
+
run_sync(self._ensure_loaded())
|
|
150
151
|
|
|
151
152
|
@staticmethod
|
|
152
153
|
async def _await_durable(operation: Awaitable[None]) -> None:
|
|
@@ -192,17 +193,17 @@ class Session:
|
|
|
192
193
|
await self._commit(lambda conversation: conversation.append_notebook(notebook, turn_id))
|
|
193
194
|
|
|
194
195
|
def _append_event_sync(self, event_type: str, **data: Any) -> None:
|
|
195
|
-
|
|
196
|
+
run_sync(self._append_event(event_type, **data))
|
|
196
197
|
|
|
197
198
|
def _append_message_sync(self, message: BaseMessage, turn_id: str) -> None:
|
|
198
|
-
|
|
199
|
+
run_sync(self._append_message(message, turn_id))
|
|
199
200
|
|
|
200
201
|
def _append_notebook_sync(
|
|
201
202
|
self,
|
|
202
203
|
notebook: list[dict[str, Any]] | None,
|
|
203
204
|
turn_id: str,
|
|
204
205
|
) -> None:
|
|
205
|
-
|
|
206
|
+
run_sync(self._append_notebook(notebook, turn_id))
|
|
206
207
|
|
|
207
208
|
def _model_kwargs(self, model: BaseChatModel) -> dict[str, Any]:
|
|
208
209
|
if self.conversation is None:
|
|
@@ -316,7 +317,7 @@ class Session:
|
|
|
316
317
|
except TransientProviderError as error:
|
|
317
318
|
if retry == len(_MODEL_RETRY_DELAYS):
|
|
318
319
|
raise
|
|
319
|
-
|
|
320
|
+
run_sync(self._reset_model_connection(model))
|
|
320
321
|
time.sleep(max(0.0, error.retry_after or _MODEL_RETRY_DELAYS[retry]))
|
|
321
322
|
raise AssertionError("unreachable")
|
|
322
323
|
|
|
@@ -434,7 +435,7 @@ class Session:
|
|
|
434
435
|
try:
|
|
435
436
|
asyncio.get_running_loop()
|
|
436
437
|
except RuntimeError:
|
|
437
|
-
|
|
438
|
+
run_sync(self.aclear())
|
|
438
439
|
return
|
|
439
440
|
raise RuntimeError("clear() cannot run inside an active event loop; use await aclear().")
|
|
440
441
|
|
|
@@ -459,11 +460,14 @@ class Session:
|
|
|
459
460
|
) -> None:
|
|
460
461
|
"""Send one user message synchronously."""
|
|
461
462
|
try:
|
|
462
|
-
asyncio.get_running_loop()
|
|
463
|
+
loop = asyncio.get_running_loop()
|
|
463
464
|
except RuntimeError:
|
|
464
465
|
pass
|
|
465
466
|
else:
|
|
466
|
-
|
|
467
|
+
# Greenlet-backed sync APIs can expose their loop on this thread
|
|
468
|
+
# without this call actually running inside an asyncio task.
|
|
469
|
+
if asyncio.current_task(loop) is not None:
|
|
470
|
+
raise RuntimeError("send() cannot run inside an active event loop; use await asend().")
|
|
467
471
|
|
|
468
472
|
text = prompt.strip()
|
|
469
473
|
if not text:
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import ast
|
|
6
|
+
import asyncio
|
|
6
7
|
import sys
|
|
7
8
|
from collections.abc import Callable, Iterator
|
|
9
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
8
10
|
from contextlib import contextmanager
|
|
9
11
|
from copy import deepcopy
|
|
10
12
|
from unittest.mock import patch
|
|
@@ -326,12 +328,36 @@ def install_question_mode(shell: TerminalInteractiveShell) -> Callable[[], None]
|
|
|
326
328
|
question_mode = False
|
|
327
329
|
active_prompt = False
|
|
328
330
|
pending_question = False
|
|
331
|
+
prompt_worker: ThreadPoolExecutor | None = None
|
|
329
332
|
|
|
330
333
|
def prompt_for_code() -> str:
|
|
331
|
-
nonlocal active_prompt, pending_question
|
|
334
|
+
nonlocal active_prompt, pending_question, prompt_worker
|
|
332
335
|
active_prompt = True
|
|
333
336
|
try:
|
|
334
|
-
|
|
337
|
+
try:
|
|
338
|
+
asyncio.get_running_loop()
|
|
339
|
+
except RuntimeError:
|
|
340
|
+
text = original_prompt_for_code()
|
|
341
|
+
else:
|
|
342
|
+
if prompt_worker is None:
|
|
343
|
+
prompt_worker = ThreadPoolExecutor(max_workers=1, thread_name_prefix="codebind-prompt")
|
|
344
|
+
future = prompt_worker.submit(original_prompt_for_code)
|
|
345
|
+
try:
|
|
346
|
+
text = future.result()
|
|
347
|
+
except BaseException:
|
|
348
|
+
if not future.done():
|
|
349
|
+
app = prompt_session.app
|
|
350
|
+
if app.is_running and app.loop is not None:
|
|
351
|
+
def abort_prompt() -> None:
|
|
352
|
+
if app.is_running:
|
|
353
|
+
app.exit(exception=KeyboardInterrupt())
|
|
354
|
+
|
|
355
|
+
app.loop.call_soon_threadsafe(abort_prompt)
|
|
356
|
+
try:
|
|
357
|
+
future.result(timeout=3)
|
|
358
|
+
except BaseException:
|
|
359
|
+
pass
|
|
360
|
+
raise
|
|
335
361
|
finally:
|
|
336
362
|
active_prompt = False
|
|
337
363
|
pending_question = question_mode and bool(text.strip())
|
|
@@ -391,6 +417,8 @@ def install_question_mode(shell: TerminalInteractiveShell) -> Callable[[], None]
|
|
|
391
417
|
prompt_session.style = original_style
|
|
392
418
|
prompt_session.mouse_support = original_prompt_mouse
|
|
393
419
|
shell.mouse_support = original_shell_mouse
|
|
420
|
+
if prompt_worker is not None:
|
|
421
|
+
prompt_worker.shutdown(wait=False, cancel_futures=True)
|
|
394
422
|
|
|
395
423
|
return restore
|
|
396
424
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|