Skip to content
This repository was archived by the owner on Jun 5, 2025. It is now read-only.

Commit e989015

Browse files
Adds cache to cli-chat commands (#738)
* Adds cache to cli-chat commands Closes: #732 Copilot sends at least 2 requests with the same `last_user_message`. Hence we execute the same command 2 times and reply to the last one. The last behaviour will cause that if we create a Workspace with Copilot the cli will respond that the workspace already exists. This PR implements a workaround to cache the commands and it's outputs. That way we can reply the same to subsequent requests sent by Copilot * Updated cache to use cachetools.TTLCache * updated lock file
1 parent 2f60c6d commit e989015

File tree

3 files changed

+60
-4
lines changed

3 files changed

+60
-4
lines changed

poetry.lock

Lines changed: 15 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ alembic = "==1.14.1"
3030
pygments = "==2.19.1"
3131
sqlite-vec-sl-tmp = "==0.0.4"
3232
greenlet = "==3.1.1"
33+
cachetools = "==5.5.1"
3334

3435
[tool.poetry.group.dev.dependencies]
3536
pytest = "==8.3.4"

src/codegate/pipeline/cli/commands.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from abc import ABC, abstractmethod
2-
from typing import Awaitable, Callable, Dict, List, Tuple
2+
from typing import Awaitable, Callable, Dict, List, Optional, Tuple
33

4+
from cachetools import TTLCache
45
from pydantic import ValidationError
56

67
from codegate import __version__
@@ -16,6 +17,11 @@ class NoSubcommandError(Exception):
1617
pass
1718

1819

20+
# 1 second cache. 1 second is to be short enough to not affect UX but long enough to
21+
# reply the same to concurrent requests. Needed for Copilot.
22+
command_cache = TTLCache(maxsize=10, ttl=1)
23+
24+
1925
class CodegateCommand(ABC):
2026
@abstractmethod
2127
async def run(self, args: List[str]) -> str:
@@ -31,10 +37,46 @@ def command_name(self) -> str:
3137
def help(self) -> str:
3238
pass
3339

40+
async def _get_full_command(self, args: List[str]) -> str:
41+
"""
42+
Get the full command string with the command name and args.
43+
"""
44+
joined_args = " ".join(args)
45+
return f"{self.command_name} {joined_args}"
46+
47+
async def _record_in_cache(self, args: List[str], cmd_out: str) -> None:
48+
"""
49+
Record the command in the cache.
50+
"""
51+
full_command = await self._get_full_command(args)
52+
command_cache[full_command] = cmd_out
53+
54+
async def _cache_lookup(self, args: List[str]) -> Optional[str]:
55+
"""
56+
Look up the command in the cache. If the command was executed less than 1 second ago,
57+
return the cached output.
58+
"""
59+
full_command = await self._get_full_command(args)
60+
cmd_out = command_cache.get(full_command)
61+
return cmd_out
62+
3463
async def exec(self, args: List[str]) -> str:
64+
"""
65+
Execute the command and cache the output. The cache is invalidated after 1 second.
66+
67+
1. Check if the command is help. If it is, return the help text.
68+
2. Check if the command is in the cache. If it is, return the cached output.
69+
3. Run the command and cache the output.
70+
4. Return the output.
71+
"""
3572
if args and args[0] == "-h":
3673
return self.help
37-
return await self.run(args)
74+
cached_out = await self._cache_lookup(args)
75+
if cached_out:
76+
return cached_out
77+
cmd_out = await self.run(args)
78+
await self._record_in_cache(args, cmd_out)
79+
return cmd_out
3880

3981

4082
class Version(CodegateCommand):

0 commit comments

Comments
 (0)