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

Commit 3e55e18

Browse files
committed
Merge branch 'main' of github.com:stacklok/codegate into feat/555/version-endpoint
2 parents 941e824 + e1b3b4c commit 3e55e18

File tree

12 files changed

+72
-41
lines changed

12 files changed

+72
-41
lines changed

.github/workflows/image-publish.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ jobs:
8787
cache-to: type=gha,mode=max
8888
build-args: |
8989
LATEST_RELEASE=${{ env.LATEST_RELEASE }}
90+
CODEGATE_VERSION=${{ steps.version-string.outputs.tag }}
9091
- name: Capture Image Digest
9192
id: image-digest
9293
run: |

Dockerfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Builder stage: Install dependencies and build the application
22
FROM python:3.12-slim AS builder
33

4+
ARG CODEGATE_VERSION=dev
5+
46
# Install system dependencies
57
RUN apt-get update && apt-get install -y --no-install-recommends \
68
gcc \
@@ -21,6 +23,9 @@ RUN poetry config virtualenvs.create false && \
2123
# Copy the rest of the application
2224
COPY . /app
2325

26+
# Overwrite the _VERSION variable in the code
27+
RUN sed -i "s/_VERSION =.*/_VERSION = \"${CODEGATE_VERSION}\"/g" /app/src/codegate/__init__.py
28+
2429
# Build the webapp
2530
FROM node:23-slim AS webbuilder
2631

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ image-build:
3434
DOCKER_BUILDKIT=1 $(CONTAINER_BUILD) \
3535
-f Dockerfile \
3636
--build-arg LATEST_RELEASE=$(curl -s "https://api.github.com/repos/stacklok/codegate-ui/releases/latest" | grep '"zipball_url":' | cut -d '"' -f 4) \
37+
--build-arg CODEGATE_VERSION="$(shell git describe --tags --abbrev=0)-$(shell git rev-parse --short HEAD)-dev" \
3738
-t codegate \
3839
. \
3940
-t ghcr.io/stacklok/codegate:$(VER) \

signatures.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
---
22
- Amazon:
33
- Access Key: (?:A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA|ABIA|ACCA)[A-Z0-9]{16}
4-
- Secret Access Key: (?<![A-Za-z0-9/+])[A-Za-z0-9+=][A-Za-z0-9/+=]{38}[A-Za-z0-9+=](?![A-Za-z0-9/+=])
54
# - Cognito User Pool ID: (?i)us-[a-z]{2,}-[a-z]{4,}-\d{1,}
65
- RDS Password: (?i)(rds\-master\-password|db\-password)
76
- SNS Confirmation URL: (?i)https:\/\/sns\.[a-z0-9-]+\.amazonaws\.com\/?Action=ConfirmSubscription&Token=[a-zA-Z0-9-=_]+

src/codegate/__init__.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,19 @@
77
from codegate.config import Config
88
from codegate.exceptions import ConfigurationError
99

10-
try:
11-
__version__ = metadata.version("codegate")
12-
__description__ = metadata.metadata("codegate")["Summary"]
13-
except metadata.PackageNotFoundError: # pragma: no cover
14-
__version__ = "unknown"
15-
__description__ = "codegate"
10+
_VERSION = "dev"
11+
_DESC = "CodeGate - A Generative AI security gateway."
12+
13+
def __get_version_and_description() -> tuple[str, str]:
14+
try:
15+
version = metadata.version("codegate")
16+
description = metadata.metadata("codegate")["Summary"]
17+
except metadata.PackageNotFoundError:
18+
version = _VERSION
19+
description = _DESC
20+
return version, description
21+
22+
__version__, __description__ = __get_version_and_description()
1623

1724
__all__ = ["Config", "ConfigurationError", "LogFormat", "LogLevel", "setup_logging"]
1825

src/codegate/pipeline/base.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -239,36 +239,38 @@ def get_last_user_message(
239239
@staticmethod
240240
def get_last_user_message_block(
241241
request: ChatCompletionRequest,
242-
) -> Optional[str]:
242+
) -> Optional[tuple[str, int]]:
243243
"""
244244
Get the last block of consecutive 'user' messages from the request.
245245
246246
Args:
247247
request (ChatCompletionRequest): The chat completion request to process
248248
249249
Returns:
250-
Optional[str]: A string containing all consecutive user messages in the
250+
Optional[str, int]: A string containing all consecutive user messages in the
251251
last user message block, separated by newlines, or None if
252252
no user message block is found.
253+
Index of the first message detected in the block.
253254
"""
254255
if request.get("messages") is None:
255256
return None
256257

257258
user_messages = []
258259
messages = request["messages"]
260+
block_start_index = None
259261

260262
# Iterate in reverse to find the last block of consecutive 'user' messages
261263
for i in reversed(range(len(messages))):
262264
if messages[i]["role"] == "user" or messages[i]["role"] == "assistant":
263-
content_str = None
264-
if "content" in messages[i]:
265-
content_str = messages[i]["content"] # type: ignore
266-
else:
265+
content_str = messages[i].get("content")
266+
if content_str is None:
267267
continue
268268

269269
if messages[i]["role"] == "user":
270270
user_messages.append(content_str)
271-
# specifically for Aider, when "ok." block is found, stop
271+
block_start_index = i
272+
273+
# Specifically for Aider, when "Ok." block is found, stop
272274
if content_str == "Ok." and messages[i]["role"] == "assistant":
273275
break
274276
else:
@@ -277,8 +279,9 @@ def get_last_user_message_block(
277279
break
278280

279281
# Reverse the collected user messages to preserve the original order
280-
if user_messages:
281-
return "\n".join(reversed(user_messages))
282+
if user_messages and block_start_index is not None:
283+
content = "\n".join(reversed(user_messages))
284+
return content, block_start_index
282285

283286
return None
284287

src/codegate/pipeline/codegate_context_retriever/codegate.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,10 @@ async def process(
6060
Use RAG DB to add context to the user request
6161
"""
6262
# Get the latest user message
63-
user_message = self.get_last_user_message_block(request)
64-
if not user_message:
63+
last_message = self.get_last_user_message_block(request)
64+
if not last_message:
6565
return PipelineResult(request=request)
66+
user_message, _ = last_message
6667

6768
# Create storage engine object
6869
storage_engine = StorageEngine()

src/codegate/pipeline/extract_snippets/extract_snippets.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,10 @@ async def process(
151151
request: ChatCompletionRequest,
152152
context: PipelineContext,
153153
) -> PipelineResult:
154-
msg_content = self.get_last_user_message_block(request)
155-
if not msg_content:
154+
last_message = self.get_last_user_message_block(request)
155+
if not last_message:
156156
return PipelineResult(request=request, context=context)
157+
msg_content, _ = last_message
157158
snippets = extract_snippets(msg_content)
158159

159160
logger.info(f"Extracted {len(snippets)} code snippets from the user message")

src/codegate/pipeline/secrets/secrets.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -271,11 +271,12 @@ async def process(
271271
new_request = request.copy()
272272
total_matches = []
273273

274-
# Process all messages
274+
# get last user message block to get index for the first relevant user message
275+
last_user_message = self.get_last_user_message_block(new_request)
275276
last_assistant_idx = -1
276-
for i, message in enumerate(new_request["messages"]):
277-
if message.get("role", "") == "assistant":
278-
last_assistant_idx = i
277+
if last_user_message:
278+
_, user_idx = last_user_message
279+
last_assistant_idx = user_idx - 1
279280

280281
# Process all messages
281282
for i, message in enumerate(new_request["messages"]):
@@ -312,8 +313,8 @@ class SecretUnredactionStep(OutputPipelineStep):
312313
"""Pipeline step that unredacts protected content in the stream"""
313314

314315
def __init__(self):
315-
self.redacted_pattern = re.compile(r"REDACTED<\$([^>]+)>")
316-
self.marker_start = "REDACTED<$"
316+
self.redacted_pattern = re.compile(r"REDACTED<(\$?[^>]+)>")
317+
self.marker_start = "REDACTED<"
317318
self.marker_end = ">"
318319

319320
@property
@@ -365,6 +366,8 @@ async def process_chunk(
365366
if match:
366367
# Found a complete marker, process it
367368
encrypted_value = match.group(1)
369+
if encrypted_value.startswith('$'):
370+
encrypted_value = encrypted_value[1:]
368371
original_value = input_context.sensitive.manager.get_original_value(
369372
encrypted_value,
370373
input_context.sensitive.session_id,
@@ -399,7 +402,7 @@ async def process_chunk(
399402
return []
400403

401404
if self._is_partial_marker_prefix(buffered_content):
402-
context.prefix_buffer += buffered_content
405+
context.prefix_buffer = buffered_content
403406
return []
404407

405408
# No markers or partial markers, let pipeline handle the chunk normally

src/codegate/providers/ollama/completion_handler.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
from typing import AsyncIterator, Optional, Union
23

34
import structlog
@@ -11,18 +12,21 @@
1112

1213

1314
async def ollama_stream_generator(
14-
stream: AsyncIterator[ChatResponse],
15+
stream: AsyncIterator[ChatResponse]
1516
) -> AsyncIterator[str]:
1617
"""OpenAI-style SSE format"""
1718
try:
1819
async for chunk in stream:
19-
print(chunk)
2020
try:
21-
yield f"{chunk.model_dump_json()}\n\n"
21+
content = chunk.model_dump_json()
22+
if content:
23+
yield f"{chunk.model_dump_json()}\n"
2224
except Exception as e:
23-
yield f"{str(e)}\n\n"
25+
if str(e):
26+
yield f"{str(e)}\n"
2427
except Exception as e:
25-
yield f"{str(e)}\n\n"
28+
if str(e):
29+
yield f"{str(e)}\n"
2630

2731

2832
class OllamaShim(BaseCompletionHandler):
@@ -39,17 +43,17 @@ async def execute_completion(
3943
) -> Union[ChatResponse, GenerateResponse]:
4044
"""Stream response directly from Ollama API."""
4145
if is_fim_request:
42-
prompt = request["messages"][0]["content"]
46+
prompt = request["messages"][0].get("content", "")
4347
response = await self.client.generate(
44-
model=request["model"], prompt=prompt, stream=stream, options=request["options"]
48+
model=request["model"], prompt=prompt, stream=stream, options=request["options"] # type: ignore
4549
)
4650
else:
4751
response = await self.client.chat(
4852
model=request["model"],
4953
messages=request["messages"],
50-
stream=stream,
51-
options=request["options"],
52-
)
54+
stream=stream, # type: ignore
55+
options=request["options"], # type: ignore
56+
) # type: ignore
5357
return response
5458

5559
def _create_streaming_response(self, stream: AsyncIterator[ChatResponse]) -> StreamingResponse:

0 commit comments

Comments
 (0)