Skip to content

Commit 06c8aa4

Browse files
authored
feat: add bot option flag (#111)
1 parent 86e9712 commit 06c8aa4

File tree

6 files changed

+67
-38
lines changed

6 files changed

+67
-38
lines changed

docs/git-draft.1.adoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ If a bot needs more information, its question will be persisted and displayed wh
6666
Set bot name.
6767
The default is to use the first bot defined in the configuration.
6868

69+
-o OPTION::
70+
--bot-option=OPTION::
71+
Set bot option.
72+
Each option has the format `<key>[=<value>]` (if the value is omitted, it will default to true).
73+
This will override any option set in configuration files.
74+
6975
-e::
7076
--edit::
7177
Enable interactive editing of draft prompts and templates.

src/git_draft/__main__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ def callback(
110110
dest="bot",
111111
help="AI bot name",
112112
)
113+
parser.add_option(
114+
"-o",
115+
"--bot-option",
116+
action="append",
117+
dest="bot_options",
118+
help="AI bot options",
119+
)
113120
parser.add_option(
114121
"-e",
115122
"--edit",
@@ -193,7 +200,7 @@ async def run() -> None: # noqa: PLR0912 PLR0915
193200
bot_config = bot_configs[0]
194201
elif config.bots:
195202
bot_config = config.bots[0]
196-
bot = load_bot(bot_config)
203+
bot = load_bot(bot_config, overrides=opts.bot_options)
197204

198205
prompt: str | TemplatedPrompt
199206
if args:

src/git_draft/bots/__init__.py

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
"""Bot interfaces and built-in implementations"""
22

3+
from collections.abc import Sequence
34
import importlib
4-
import os
55
import sys
66

7-
from ..common import BotConfig, reindent
7+
from ..common import (
8+
BotConfig,
9+
JSONObject,
10+
JSONValue,
11+
UnreachableError,
12+
reindent,
13+
)
814
from .common import ActionSummary, Bot, Goal, UserFeedback, Worktree
915

1016

@@ -17,10 +23,15 @@
1723
]
1824

1925

20-
def load_bot(config: BotConfig | None) -> Bot:
26+
def load_bot(
27+
config: BotConfig | None, *, overrides: Sequence[str] = ()
28+
) -> Bot:
2129
"""Load and return a Bot instance using the provided configuration"""
30+
options = {**config.options} if config and config.options else {}
31+
options.update(_parse_overrides(overrides))
32+
2233
if not config:
23-
return _default_bot()
34+
return _default_bot(options)
2435

2536
if config.pythonpath and config.pythonpath not in sys.path:
2637
sys.path.insert(0, config.pythonpath)
@@ -35,34 +46,33 @@ def load_bot(config: BotConfig | None) -> Bot:
3546
if not factory:
3647
raise NotImplementedError(f"Unknown bot factory: {config.factory}")
3748

38-
kwargs = config.kwargs or {}
39-
return factory(**kwargs)
49+
return factory(**options)
4050

4151

42-
def _default_bot() -> Bot:
43-
if not os.environ.get("OPENAI_API_KEY"):
44-
raise RuntimeError(
45-
reindent(
46-
"""
47-
The default bot implementation requires an OpenAI API key.
48-
Please specify one via the `$OPENAI_API_KEY` environment
49-
variable or enable a different bot in your configuration.
50-
"""
51-
)
52-
)
52+
def _parse_overrides(overrides: Sequence[str]) -> JSONObject:
53+
options = dict[str, JSONValue]()
54+
for override in overrides:
55+
match override.split("=", 1):
56+
case [switch]:
57+
options[switch] = True
58+
case [flag, value]:
59+
options[flag] = value
60+
case _:
61+
raise UnreachableError()
62+
return options
63+
5364

65+
def _default_bot(options: JSONObject) -> Bot:
5466
try:
55-
from .openai_api import new_threads_bot
67+
from .openai_api import new_completions_bot
5668

5769
except ImportError:
5870
raise RuntimeError(
59-
reindent(
60-
"""
61-
The default bot implementation requires the `openai` Python
62-
package. Please install it or specify a different bot in
63-
your configuration.
64-
"""
65-
)
71+
reindent("""
72+
The default bot implementation requires the `openai` Python
73+
package. Please install it or specify a different bot in
74+
your configuration.
75+
""")
6676
)
6777
else:
68-
return new_threads_bot()
78+
return new_completions_bot(**options)

src/git_draft/common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class BotConfig:
6868

6969
factory: str
7070
name: str | None = None
71-
kwargs: JSONObject | None = None
71+
options: JSONObject | None = None
7272
pythonpath: str | None = None
7373

7474

tests/git_draft/bots/__init___test.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88

99

1010
class FakeBot(sut.Bot):
11-
pass
11+
def __init__(self, key: str="default", switch: bool=False) -> None:
12+
self.key = key
13+
self.switch = switch
1214

1315

1416
class TestLoadBot:
@@ -20,15 +22,19 @@ def import_module(name):
2022
monkeypatch.setattr(importlib, "import_module", import_module)
2123

2224
config = BotConfig(factory="fake_module:FakeBot")
23-
bot = sut.load_bot(config)
24-
assert isinstance(bot, FakeBot)
25+
26+
bot0 = sut.load_bot(config)
27+
assert isinstance(bot0, FakeBot)
28+
assert bot0.key == "default"
29+
assert not bot0.switch
30+
31+
bot1 = sut.load_bot(config, overrides=["key=one", "switch"])
32+
assert isinstance(bot1, FakeBot)
33+
assert bot1.key == "one"
34+
assert bot1.switch
35+
2536

2637
def test_non_existing_factory(self) -> None:
2738
config = BotConfig("git_draft:unknown_factory")
2839
with pytest.raises(NotImplementedError):
2940
sut.load_bot(config)
30-
31-
def test_default_no_key(self, monkeypatch) -> None:
32-
monkeypatch.setenv("OPENAI_API_KEY", "")
33-
with pytest.raises(RuntimeError):
34-
sut.load_bot(None)

tests/git_draft/common_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def test_load_ok(self) -> None:
3030
[[bots]]
3131
name = "bar"
3232
factory = "bar"
33-
kwargs = {one=1}
33+
options = {one=1}
3434
"""
3535
path = sut.Config.folder_path()
3636
path.mkdir(parents=True, exist_ok=True)
@@ -42,7 +42,7 @@ def test_load_ok(self) -> None:
4242
log_level=logging.DEBUG,
4343
bots=[
4444
sut.BotConfig(factory="foo:load", pythonpath="./abc"),
45-
sut.BotConfig(factory="bar", name="bar", kwargs={"one": 1}),
45+
sut.BotConfig(factory="bar", name="bar", options={"one": 1}),
4646
],
4747
)
4848

0 commit comments

Comments
 (0)