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

Commit ba2ca15

Browse files
authored
Fix codegate version for continue with DeepSeek (#982)
`continue version` didn't work with DeepSeek because for some reason they enclose the user message in a sort of an envelope. To make the change a little future-proof, we use the envelope to detect Continue as client and then in the cli command try to extract from the envelope, but fall back to the generic plain regex. Fixes: #981
1 parent dde0570 commit ba2ca15

File tree

4 files changed

+115
-1
lines changed

4 files changed

+115
-1
lines changed

src/codegate/clients/clients.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ class ClientType(Enum):
1212
COPILOT = "copilot" # Copilot client
1313
OPEN_INTERPRETER = "open_interpreter" # Open Interpreter client
1414
AIDER = "aider" # Aider client
15+
CONTINUE = "continue" # Continue client

src/codegate/clients/detector.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,24 @@ def client_name(self) -> ClientType:
160160
return ClientType.OPEN_INTERPRETER
161161

162162

163+
class ContinueDetector(BaseClientDetector):
164+
"""
165+
Detector for Continue client based on message content
166+
"""
167+
168+
def __init__(self):
169+
super().__init__()
170+
# This is a hack that really only detects Continue with DeepSeek
171+
# we should get a header or user agent for this (upstream PR pending)
172+
self.content_detector = ContentDetector(
173+
"You are an AI programming assistant, utilizing the DeepSeek Coder model"
174+
)
175+
176+
@property
177+
def client_name(self) -> ClientType:
178+
return ClientType.CONTINUE
179+
180+
163181
class CopilotDetector(BaseClientDetector):
164182
"""
165183
Detector for Copilot client based on user agent
@@ -191,6 +209,7 @@ def __init__(self):
191209
KoduDetector(),
192210
OpenInterpreter(),
193211
CopilotDetector(),
212+
ContinueDetector(),
194213
]
195214

196215
def __call__(self, func):

src/codegate/pipeline/cli/cli.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
)
1414
from codegate.pipeline.cli.commands import CustomInstructions, Version, Workspace
1515

16+
codegate_regex = re.compile(r"^codegate(?:\s+(.*))?", re.IGNORECASE)
17+
1618
HELP_TEXT = """
1719
## CodeGate CLI\n
1820
**Usage**: `codegate [-h] <command> [args]`\n
@@ -77,6 +79,22 @@ def _get_cli_from_open_interpreter(last_user_message_str: str) -> Optional[re.Ma
7779
return re.match(r"^codegate\s*(.*?)\s*$", last_user_block, re.IGNORECASE)
7880

7981

82+
def _get_cli_from_continue(last_user_message_str: str) -> Optional[re.Match[str]]:
83+
"""
84+
Continue sends a differently formatted message to the CLI if DeepSeek is used
85+
"""
86+
deepseek_match = re.search(
87+
r"utilizing the DeepSeek Coder model.*?### Instruction:\s*codegate\s+(.*?)\s*### Response:",
88+
last_user_message_str,
89+
re.DOTALL | re.IGNORECASE,
90+
)
91+
if deepseek_match:
92+
command = deepseek_match.group(1).strip()
93+
return re.match(r"^(.*?)$", command) # This creates a match object with the command
94+
95+
return codegate_regex.match(last_user_message_str)
96+
97+
8098
class CodegateCli(PipelineStep):
8199
"""Pipeline step that handles codegate cli."""
82100

@@ -110,12 +128,14 @@ async def process(
110128
if last_user_message is not None:
111129
last_user_message_str, _ = last_user_message
112130
last_user_message_str = last_user_message_str.strip()
113-
codegate_regex = re.compile(r"^codegate(?:\s+(.*))?", re.IGNORECASE)
114131

132+
# Check client-specific matchers first
115133
if context.client in [ClientType.CLINE, ClientType.KODU]:
116134
match = _get_cli_from_cline(codegate_regex, last_user_message_str)
117135
elif context.client in [ClientType.OPEN_INTERPRETER]:
118136
match = _get_cli_from_open_interpreter(last_user_message_str)
137+
elif context.client in [ClientType.CONTINUE]:
138+
match = _get_cli_from_continue(last_user_message_str)
119139
else:
120140
# Check if "codegate" is the first word in the message
121141
match = codegate_regex.match(last_user_message_str)

tests/clients/test_detector.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
BaseClientDetector,
1111
ClineDetector,
1212
ContentDetector,
13+
ContinueDetector,
1314
CopilotDetector,
1415
DetectClient,
1516
HeaderDetector,
@@ -291,6 +292,79 @@ async def test_missing_user_agent(self, mock_request):
291292
assert await detector.detect(mock_request) is False
292293

293294

295+
class TestContinueDetector:
296+
@pytest.mark.asyncio
297+
async def test_successful_detection_via_system_message(self, mock_request):
298+
detector = ContinueDetector()
299+
300+
async def get_json():
301+
return {
302+
"system": "You are an AI programming assistant, utilizing the DeepSeek Coder model"
303+
}
304+
305+
mock_request.json = get_json
306+
assert await detector.detect(mock_request) is True
307+
assert detector.client_name == ClientType.CONTINUE
308+
309+
@pytest.mark.asyncio
310+
async def test_detection_in_message_content(self, mock_request):
311+
detector = ContinueDetector()
312+
313+
async def get_json():
314+
return {
315+
"messages": [
316+
{
317+
"content": "You are an AI programming assistant, utilizing the DeepSeek Coder model" # noqa
318+
}
319+
]
320+
}
321+
322+
mock_request.json = get_json
323+
assert await detector.detect(mock_request) is True
324+
325+
@pytest.mark.asyncio
326+
async def test_failed_detection_with_partial_match(self, mock_request):
327+
detector = ContinueDetector()
328+
329+
async def get_json():
330+
return {"system": "You are an AI assistant"}
331+
332+
mock_request.json = get_json
333+
assert await detector.detect(mock_request) is False
334+
335+
@pytest.mark.asyncio
336+
async def test_case_insensitive_match_handling(self, mock_request):
337+
detector = ContinueDetector()
338+
339+
async def get_json():
340+
return {
341+
"system": "you ARE an ai programming assistant, UTILIZING the deepseek coder MODEL"
342+
}
343+
344+
mock_request.json = get_json
345+
assert await detector.detect(mock_request) is False # Should be case-sensitive
346+
347+
@pytest.mark.asyncio
348+
async def test_empty_system_message(self, mock_request):
349+
detector = ContinueDetector()
350+
351+
async def get_json():
352+
return {"system": ""}
353+
354+
mock_request.json = get_json
355+
assert await detector.detect(mock_request) is False
356+
357+
@pytest.mark.asyncio
358+
async def test_malformed_system_field(self, mock_request):
359+
detector = ContinueDetector()
360+
361+
async def get_json():
362+
return {"system": {"nested": "You are an AI programming assistant"}}
363+
364+
mock_request.json = get_json
365+
assert await detector.detect(mock_request) is False
366+
367+
294368
class TestDetectClient:
295369
@pytest.mark.asyncio
296370
async def test_successful_client_detection(self, mock_request):

0 commit comments

Comments
 (0)