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

Commit 44fcd1d

Browse files
authored
Merge branch 'main' into sqlite-vec
2 parents 2c063ee + c35d3b1 commit 44fcd1d

File tree

17 files changed

+427
-105
lines changed

17 files changed

+427
-105
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
python-version: ["3.12"]
1515

1616
steps:
17-
- name: Checkout github repo
17+
- name: Checkout
1818
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
1919
with:
2020
lfs: true
@@ -47,9 +47,6 @@ jobs:
4747
- name: Run linting
4848
run: make lint
4949

50-
- name: Run formatting
51-
run: make format
52-
5350
- name: Run tests
5451
run: make test
5552

.github/workflows/integration-tests.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ jobs:
2929
with:
3030
lfs: true
3131

32+
- name: Checkout LFS objects
33+
run: git lfs pull
34+
3235
- name: Ensure file permissions for mounted volume
3336
run: |
3437
mkdir -p ./codegate_volume/certs ./codegate_volume/models ./codegate_volume/db

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ format:
1919
poetry run ruff check --fix .
2020

2121
lint:
22+
poetry run black --check .
2223
poetry run ruff check .
2324

2425
test:

api/openapi.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,9 @@
172172
"description": "Successful Response",
173173
"content": {
174174
"application/json": {
175-
"schema": {}
175+
"schema": {
176+
"$ref": "#/components/schemas/Workspace"
177+
}
176178
}
177179
}
178180
},
-20.7 MB
Binary file not shown.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""add_workspace_system_prompt
2+
3+
Revision ID: a692c8b52308
4+
Revises: 5c2f3eee5f90
5+
Create Date: 2025-01-17 16:33:58.464223
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
from alembic import op
12+
13+
# revision identifiers, used by Alembic.
14+
revision: str = "a692c8b52308"
15+
down_revision: Union[str, None] = "5c2f3eee5f90"
16+
branch_labels: Union[str, Sequence[str], None] = None
17+
depends_on: Union[str, Sequence[str], None] = None
18+
19+
20+
def upgrade() -> None:
21+
# Add column to workspaces table
22+
op.execute("ALTER TABLE workspaces ADD COLUMN system_prompt TEXT DEFAULT NULL;")
23+
24+
25+
def downgrade() -> None:
26+
op.execute("ALTER TABLE workspaces DROP COLUMN system_prompt;")

src/codegate/api/v1.py

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1-
from fastapi import APIRouter, Response
2-
from fastapi.exceptions import HTTPException
1+
from fastapi import APIRouter, HTTPException, Response
32
from fastapi.routing import APIRoute
43
from pydantic import ValidationError
54

65
from codegate.api import v1_models
7-
from codegate.db.connection import AlreadyExistsError
8-
from codegate.workspaces.crud import WorkspaceCrud
96
from codegate.api.dashboard.dashboard import dashboard_router
7+
from codegate.db.connection import AlreadyExistsError
8+
from codegate.workspaces import crud
109

1110
v1 = APIRouter()
1211
v1.include_router(dashboard_router)
13-
14-
wscrud = WorkspaceCrud()
12+
wscrud = crud.WorkspaceCrud()
1513

1614

1715
def uniq_name(route: APIRoute):
@@ -44,32 +42,37 @@ async def list_active_workspaces() -> v1_models.ListActiveWorkspacesResponse:
4442
@v1.post("/workspaces/active", tags=["Workspaces"], generate_unique_id_function=uniq_name)
4543
async def activate_workspace(request: v1_models.ActivateWorkspaceRequest, status_code=204):
4644
"""Activate a workspace by name."""
47-
activated = await wscrud.activate_workspace(request.name)
48-
49-
# TODO: Refactor
50-
if not activated:
45+
try:
46+
await wscrud.activate_workspace(request.name)
47+
except crud.WorkspaceAlreadyActiveError:
5148
return HTTPException(status_code=409, detail="Workspace already active")
49+
except crud.WorkspaceDoesNotExistError:
50+
return HTTPException(status_code=404, detail="Workspace does not exist")
51+
except Exception:
52+
return HTTPException(status_code=500, detail="Internal server error")
5253

5354
return Response(status_code=204)
5455

5556

5657
@v1.post("/workspaces", tags=["Workspaces"], generate_unique_id_function=uniq_name, status_code=201)
57-
async def create_workspace(request: v1_models.CreateWorkspaceRequest):
58+
async def create_workspace(request: v1_models.CreateWorkspaceRequest) -> v1_models.Workspace:
5859
"""Create a new workspace."""
5960
# Input validation is done in the model
6061
try:
61-
created = await wscrud.add_workspace(request.name)
62+
_ = await wscrud.add_workspace(request.name)
6263
except AlreadyExistsError:
6364
raise HTTPException(status_code=409, detail="Workspace already exists")
6465
except ValidationError:
65-
raise HTTPException(status_code=400,
66-
detail=("Invalid workspace name. "
67-
"Please use only alphanumeric characters and dashes"))
66+
raise HTTPException(
67+
status_code=400,
68+
detail=(
69+
"Invalid workspace name. " "Please use only alphanumeric characters and dashes"
70+
),
71+
)
6872
except Exception:
6973
raise HTTPException(status_code=500, detail="Internal server error")
7074

71-
if created:
72-
return v1_models.Workspace(name=created.name)
75+
return v1_models.Workspace(name=request.name, is_active=False)
7376

7477

7578
@v1.delete(

src/codegate/db/connection.py

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@
3030
alert_queue = asyncio.Queue()
3131
fim_cache = FimCache()
3232

33+
3334
class AlreadyExistsError(Exception):
3435
pass
3536

37+
3638
class DbCodeGate:
3739
_instance = None
3840

@@ -246,16 +248,15 @@ async def record_context(self, context: Optional[PipelineContext]) -> None:
246248
except Exception as e:
247249
logger.error(f"Failed to record context: {context}.", error=str(e))
248250

249-
async def add_workspace(self, workspace_name: str) -> Optional[Workspace]:
251+
async def add_workspace(self, workspace_name: str) -> Workspace:
250252
"""Add a new workspace to the DB.
251253
252254
This handles validation and insertion of a new workspace.
253255
254256
It may raise a ValidationError if the workspace name is invalid.
255257
or a AlreadyExistsError if the workspace already exists.
256258
"""
257-
workspace = Workspace(id=str(uuid.uuid4()), name=workspace_name)
258-
259+
workspace = Workspace(id=str(uuid.uuid4()), name=workspace_name, system_prompt=None)
259260
sql = text(
260261
"""
261262
INSERT INTO workspaces (id, name)
@@ -266,12 +267,28 @@ async def add_workspace(self, workspace_name: str) -> Optional[Workspace]:
266267

267268
try:
268269
added_workspace = await self._execute_update_pydantic_model(
269-
workspace, sql, should_raise=True)
270+
workspace, sql, should_raise=True
271+
)
270272
except IntegrityError as e:
271273
logger.debug(f"Exception type: {type(e)}")
272274
raise AlreadyExistsError(f"Workspace {workspace_name} already exists.")
273275
return added_workspace
274276

277+
async def update_workspace(self, workspace: Workspace) -> Workspace:
278+
sql = text(
279+
"""
280+
UPDATE workspaces SET
281+
name = :name,
282+
system_prompt = :system_prompt
283+
WHERE id = :id
284+
RETURNING *
285+
"""
286+
)
287+
updated_workspace = await self._execute_update_pydantic_model(
288+
workspace, sql, should_raise=True
289+
)
290+
return updated_workspace
291+
275292
async def update_session(self, session: Session) -> Optional[Session]:
276293
sql = text(
277294
"""
@@ -284,7 +301,7 @@ async def update_session(self, session: Session) -> Optional[Session]:
284301
"""
285302
)
286303
# We only pass an object to respect the signature of the function
287-
active_session = await self._execute_update_pydantic_model(session, sql)
304+
active_session = await self._execute_update_pydantic_model(session, sql, should_raise=True)
288305
return active_session
289306

290307

@@ -317,14 +334,21 @@ async def _execute_select_pydantic_model(
317334
return None
318335

319336
async def _exec_select_conditions_to_pydantic(
320-
self, model_type: Type[BaseModel], sql_command: TextClause, conditions: dict
337+
self,
338+
model_type: Type[BaseModel],
339+
sql_command: TextClause,
340+
conditions: dict,
341+
should_raise: bool = False,
321342
) -> Optional[List[BaseModel]]:
322343
async with self._async_db_engine.begin() as conn:
323344
try:
324345
result = await conn.execute(sql_command, conditions)
325346
return await self._dump_result_to_pydantic_model(model_type, result)
326347
except Exception as e:
327348
logger.error(f"Failed to select model with conditions: {model_type}.", error=str(e))
349+
# Exposes errors to the caller
350+
if should_raise:
351+
raise e
328352
return None
329353

330354
async def get_prompts_with_output(self) -> List[GetPromptWithOutputsRow]:
@@ -382,17 +406,19 @@ async def get_workspaces(self) -> List[WorkspaceActive]:
382406
workspaces = await self._execute_select_pydantic_model(WorkspaceActive, sql)
383407
return workspaces
384408

385-
async def get_workspace_by_name(self, name: str) -> List[Workspace]:
409+
async def get_workspace_by_name(self, name: str) -> Optional[Workspace]:
386410
sql = text(
387411
"""
388412
SELECT
389-
id, name
413+
id, name, system_prompt
390414
FROM workspaces
391415
WHERE name = :name
392416
"""
393417
)
394418
conditions = {"name": name}
395-
workspaces = await self._exec_select_conditions_to_pydantic(Workspace, sql, conditions)
419+
workspaces = await self._exec_select_conditions_to_pydantic(
420+
Workspace, sql, conditions, should_raise=True
421+
)
396422
return workspaces[0] if workspaces else None
397423

398424
async def get_sessions(self) -> List[Session]:
@@ -410,7 +436,7 @@ async def get_active_workspace(self) -> Optional[ActiveWorkspace]:
410436
sql = text(
411437
"""
412438
SELECT
413-
w.id, w.name, s.id as session_id, s.last_update
439+
w.id, w.name, w.system_prompt, s.id as session_id, s.last_update
414440
FROM sessions s
415441
INNER JOIN workspaces w ON w.id = s.active_workspace_id
416442
"""
@@ -453,7 +479,11 @@ def init_session_if_not_exists(db_path: Optional[str] = None):
453479
last_update=datetime.datetime.now(datetime.timezone.utc),
454480
)
455481
db_recorder = DbRecorder(db_path)
456-
asyncio.run(db_recorder.update_session(session))
482+
try:
483+
asyncio.run(db_recorder.update_session(session))
484+
except Exception as e:
485+
logger.error(f"Failed to initialize session in DB: {e}")
486+
return
457487
logger.info("Session in DB initialized successfully.")
458488

459489

src/codegate/db/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class Setting(BaseModel):
4343
class Workspace(BaseModel):
4444
id: str
4545
name: str
46+
system_prompt: Optional[str]
4647

4748
@field_validator("name", mode="plain")
4849
@classmethod
@@ -98,5 +99,6 @@ class WorkspaceActive(BaseModel):
9899
class ActiveWorkspace(BaseModel):
99100
id: str
100101
name: str
102+
system_prompt: Optional[str]
101103
session_id: str
102104
last_update: datetime.datetime

src/codegate/pipeline/cli/cli.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
PipelineResult,
99
PipelineStep,
1010
)
11-
from codegate.pipeline.cli.commands import Version, Workspace
11+
from codegate.pipeline.cli.commands import SystemPrompt, Version, Workspace
1212

1313
HELP_TEXT = """
1414
## CodeGate CLI\n
@@ -32,6 +32,7 @@ async def codegate_cli(command):
3232
available_commands = {
3333
"version": Version().exec,
3434
"workspace": Workspace().exec,
35+
"system-prompt": SystemPrompt().exec,
3536
}
3637
out_func = available_commands.get(command[0])
3738
if out_func is None:

0 commit comments

Comments
 (0)