Skip to content

Commit 690050a

Browse files
authored
refactor: rename assistant to bot (#20)
1 parent d6369f4 commit 690050a

File tree

16 files changed

+257
-106
lines changed

16 files changed

+257
-106
lines changed

src/git_draft/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import logging
22

3-
from .assistants import Assistant, Session, Toolbox
3+
from .bots import Action, Bot, Toolbox
44

55
__all__ = [
6-
"Assistant",
7-
"Session",
6+
"Action",
7+
"Bot",
88
"Toolbox",
99
]
1010

src/git_draft/__main__.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import sys
77
import textwrap
88

9-
from .assistants import load_assistant
9+
from .bots import load_bot
1010
from .common import Store, open_editor
1111
from .manager import Manager
1212

@@ -47,9 +47,9 @@ def callback(_option, _opt, _value, parser) -> None:
4747

4848
parser.add_option(
4949
"-a",
50-
"--assistant",
51-
dest="assistant",
52-
help="assistant key",
50+
"--bot",
51+
dest="bot",
52+
help="bot key",
5353
default="openai",
5454
)
5555
parser.add_option(
@@ -96,15 +96,15 @@ def main() -> None:
9696

9797
command = getattr(opts, "command", "generate")
9898
if command == "generate":
99-
assistant = load_assistant(opts.assistant, {})
99+
bot = load_bot(opts.bot, {})
100100
prompt = opts.prompt
101101
if not prompt:
102102
if sys.stdin.isatty():
103103
prompt = open_editor(textwrap.dedent(EDITOR_PLACEHOLDER))
104104
else:
105105
prompt = sys.stdin.read()
106106
manager.generate_draft(
107-
prompt, assistant, checkout=opts.checkout, reset=opts.reset
107+
prompt, bot, checkout=opts.checkout, reset=opts.reset
108108
)
109109
elif command == "finalize":
110110
manager.finalize_draft(delete=opts.delete)

src/git_draft/assistants/__init__.py

Lines changed: 0 additions & 26 deletions
This file was deleted.

src/git_draft/assistants/common.py

Lines changed: 0 additions & 32 deletions
This file was deleted.

src/git_draft/bots/__init__.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""Bot interfaces and built-in implementations
2+
3+
* https://aider.chat/docs/leaderboards/
4+
"""
5+
6+
from typing import Any, Mapping
7+
8+
from .common import Action, Bot, Toolbox
9+
10+
__all__ = [
11+
"Action",
12+
"Bot",
13+
"Toolbox",
14+
]
15+
16+
17+
def load_bot(entry: str, kwargs: Mapping[str, Any]) -> Bot:
18+
if entry == "openai":
19+
return _load_openai_bot(**kwargs)
20+
raise NotImplementedError() # TODO
21+
22+
23+
def _load_openai_bot(**kwargs) -> Bot:
24+
from .openai import OpenAIBot
25+
26+
return OpenAIBot(**kwargs)

src/git_draft/bots/common.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
from __future__ import annotations
2+
3+
import dataclasses
4+
from datetime import datetime
5+
from pathlib import PurePosixPath
6+
from typing import Callable, Sequence
7+
8+
from ..common import JSONObject
9+
10+
11+
class Toolbox:
12+
"""File-system intermediary
13+
14+
Note that the toolbox is not thread-safe. Concurrent operations should be
15+
serialized by the caller.
16+
"""
17+
18+
# TODO: Something similar to https://aider.chat/docs/repomap.html,
19+
# including inferring the most important files, and allowing returning
20+
# signature-only versions.
21+
22+
def __init__(self, hook: OperationHook | None = None) -> None:
23+
self.operations = list[Operation]()
24+
self._operation_hook = hook
25+
26+
def _record_operation(
27+
self, reason: str | None, tool: str, **kwargs
28+
) -> None:
29+
op = Operation(
30+
tool=tool, details=kwargs, reason=reason, start=datetime.now()
31+
)
32+
self.operations.append(op)
33+
if self._operation_hook:
34+
self._operation_hook(op)
35+
36+
def list_files(self, reason: str | None = None) -> Sequence[PurePosixPath]:
37+
self._record_operation(reason, "list_files")
38+
return self._list()
39+
40+
def read_file(
41+
self,
42+
path: PurePosixPath,
43+
reason: str | None = None,
44+
) -> str:
45+
self._record_operation(reason, "read_file", path=str(path))
46+
return self._read(path)
47+
48+
def write_file(
49+
self,
50+
path: PurePosixPath,
51+
contents: str,
52+
reason: str | None = None,
53+
) -> None:
54+
self._record_operation(
55+
reason, "write_file", path=str(path), size=len(contents)
56+
)
57+
return self._write(path, contents)
58+
59+
def delete_file(
60+
self,
61+
path: PurePosixPath,
62+
reason: str | None = None,
63+
) -> None:
64+
self._record_operation(reason, "delete_file", path=str(path))
65+
return self._delete(path)
66+
67+
def _list(self) -> Sequence[PurePosixPath]:
68+
raise NotImplementedError()
69+
70+
def _read(self, path: PurePosixPath) -> str:
71+
raise NotImplementedError()
72+
73+
def _write(self, path: PurePosixPath, contents: str) -> None:
74+
raise NotImplementedError()
75+
76+
def _delete(self, path: PurePosixPath) -> None:
77+
raise NotImplementedError()
78+
79+
80+
@dataclasses.dataclass(frozen=True)
81+
class Operation:
82+
tool: str
83+
details: JSONObject
84+
reason: str | None
85+
start: datetime
86+
87+
88+
type OperationHook = Callable[[Operation], None]
89+
90+
91+
@dataclasses.dataclass(frozen=True)
92+
class Action:
93+
title: str | None = None
94+
95+
96+
class Bot:
97+
def act(self, prompt: str, toolbox: Toolbox) -> Action:
98+
raise NotImplementedError()
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import textwrap
66
from typing import Any, Mapping, Self, Sequence, override
77

8-
from .common import Assistant, Session, Toolbox
8+
from .common import Action, Bot, Toolbox
99

1010

1111
_logger = logging.getLogger(__name__)
@@ -86,8 +86,8 @@ def _function_tool_param(
8686
"""
8787

8888

89-
class OpenAIAssistant(Assistant):
90-
"""An OpenAI-backed assistant
89+
class OpenAIBot(Bot):
90+
"""An OpenAI-backed bot
9191
9292
See the following links for resources:
9393
@@ -100,7 +100,7 @@ class OpenAIAssistant(Assistant):
100100
def __init__(self) -> None:
101101
self._client = openai.OpenAI()
102102

103-
def run(self, prompt: str, toolbox: Toolbox) -> Session:
103+
def act(self, prompt: str, toolbox: Toolbox) -> Action:
104104
# TODO: Reuse assistant.
105105
assistant = self._client.beta.assistants.create(
106106
instructions=_INSTRUCTIONS,
@@ -123,7 +123,7 @@ def run(self, prompt: str, toolbox: Toolbox) -> Session:
123123
) as stream:
124124
stream.until_done()
125125

126-
return Session(0)
126+
return Action()
127127

128128

129129
class _EventHandler(openai.AssistantEventHandler):

src/git_draft/common.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import contextlib
44
import dataclasses
5+
from datetime import datetime
56
import functools
67
import logging
78
import os
@@ -14,7 +15,7 @@
1415
import sys
1516
import tempfile
1617
import tomllib
17-
from typing import Iterator, Mapping, Self
18+
from typing import Any, Iterator, Mapping, Self
1819
import xdg_base_dirs
1920

2021

@@ -25,6 +26,7 @@
2526
class Config:
2627
log_level: int
2728
bots: Mapping[str, BotConfig]
29+
# TODO: Add (prompt) templates.
2830

2931
@classmethod
3032
def default(cls) -> Self:
@@ -50,10 +52,14 @@ def load(cls) -> Self:
5052
)
5153

5254

55+
type JSONValue = Any
56+
type JSONObject = Mapping[str, JSONValue]
57+
58+
5359
@dataclasses.dataclass(frozen=True)
5460
class BotConfig:
5561
loader: str
56-
kwargs: Mapping[str, bool | float | str] | None = None
62+
kwargs: JSONObject | None = None
5763
pythonpath: str | None = None
5864

5965

@@ -101,6 +107,12 @@ def open_editor(placeholder="") -> str:
101107
return reader.read()
102108

103109

110+
sqlite3.register_adapter(datetime, lambda d: d.isoformat())
111+
sqlite3.register_converter(
112+
"timestamp", lambda v: datetime.fromisoformat(v.decode())
113+
)
114+
115+
104116
class Store:
105117
_name = "db.v1.sqlite3"
106118

0 commit comments

Comments
 (0)