kodit 0.3.16__py3-none-any.whl → 0.3.17__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of kodit might be problematic. Click here for more details.

Files changed (33) hide show
  1. kodit/_version.py +3 -16
  2. kodit/app.py +11 -2
  3. kodit/application/services/auto_indexing_service.py +16 -7
  4. kodit/application/services/indexing_worker_service.py +154 -0
  5. kodit/application/services/queue_service.py +52 -0
  6. kodit/application/services/sync_scheduler.py +10 -48
  7. kodit/cli.py +407 -148
  8. kodit/cli_utils.py +74 -0
  9. kodit/config.py +33 -3
  10. kodit/domain/entities.py +48 -1
  11. kodit/domain/protocols.py +29 -2
  12. kodit/domain/value_objects.py +13 -0
  13. kodit/infrastructure/api/client/__init__.py +14 -0
  14. kodit/infrastructure/api/client/base.py +100 -0
  15. kodit/infrastructure/api/client/exceptions.py +21 -0
  16. kodit/infrastructure/api/client/generated_endpoints.py +27 -0
  17. kodit/infrastructure/api/client/index_client.py +57 -0
  18. kodit/infrastructure/api/client/search_client.py +86 -0
  19. kodit/infrastructure/api/v1/dependencies.py +13 -0
  20. kodit/infrastructure/api/v1/routers/indexes.py +9 -4
  21. kodit/infrastructure/enrichment/local_enrichment_provider.py +4 -1
  22. kodit/infrastructure/enrichment/openai_enrichment_provider.py +5 -1
  23. kodit/infrastructure/enrichment/utils.py +30 -0
  24. kodit/infrastructure/mappers/task_mapper.py +81 -0
  25. kodit/infrastructure/sqlalchemy/entities.py +35 -0
  26. kodit/infrastructure/sqlalchemy/task_repository.py +81 -0
  27. kodit/migrations/versions/9cf0e87de578_add_queue.py +47 -0
  28. kodit/utils/generate_api_paths.py +135 -0
  29. {kodit-0.3.16.dist-info → kodit-0.3.17.dist-info}/METADATA +1 -1
  30. {kodit-0.3.16.dist-info → kodit-0.3.17.dist-info}/RECORD +33 -19
  31. {kodit-0.3.16.dist-info → kodit-0.3.17.dist-info}/WHEEL +0 -0
  32. {kodit-0.3.16.dist-info → kodit-0.3.17.dist-info}/entry_points.txt +0 -0
  33. {kodit-0.3.16.dist-info → kodit-0.3.17.dist-info}/licenses/LICENSE +0 -0
kodit/cli_utils.py ADDED
@@ -0,0 +1,74 @@
1
+ """Utilities for CLI commands with remote/local mode support."""
2
+
3
+ from collections.abc import Callable
4
+ from functools import wraps
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ import click
8
+
9
+ from kodit.infrastructure.api.client import IndexClient, SearchClient
10
+
11
+ if TYPE_CHECKING:
12
+ from kodit.config import AppContext
13
+
14
+
15
+ def with_client(f: Callable) -> Callable:
16
+ """Provide appropriate client based on configuration.
17
+
18
+ This decorator automatically detects whether to run in local or remote mode
19
+ based on the presence of remote.server_url in the configuration. In remote
20
+ mode, it provides API clients. In local mode, it behaves like the existing
21
+ with_session decorator.
22
+ """
23
+
24
+ @wraps(f)
25
+ async def wrapper(*args: Any, **kwargs: Any) -> Any:
26
+ ctx = click.get_current_context()
27
+ app_context: AppContext = ctx.obj
28
+
29
+ # Auto-detect mode based on remote.server_url presence
30
+ if not app_context.is_remote:
31
+ # Local mode - use existing database session approach
32
+ from kodit.config import with_session
33
+
34
+ # Apply the session decorator to the original function
35
+ session_wrapped = with_session(f)
36
+ # Remove the async wrapper that with_session adds since we're already async
37
+ inner_func = getattr(
38
+ getattr(session_wrapped, "__wrapped__", session_wrapped),
39
+ "__wrapped__",
40
+ session_wrapped
41
+ )
42
+
43
+ # Get database session manually
44
+ db = await app_context.get_db()
45
+ async with db.session_factory() as session:
46
+ return await inner_func(session, *args, **kwargs)
47
+ else:
48
+ # Remote mode - use API clients
49
+ clients = {
50
+ "index_client": IndexClient(
51
+ base_url=app_context.remote.server_url or "",
52
+ api_key=app_context.remote.api_key,
53
+ timeout=app_context.remote.timeout,
54
+ max_retries=app_context.remote.max_retries,
55
+ verify_ssl=app_context.remote.verify_ssl,
56
+ ),
57
+ "search_client": SearchClient(
58
+ base_url=app_context.remote.server_url or "",
59
+ api_key=app_context.remote.api_key,
60
+ timeout=app_context.remote.timeout,
61
+ max_retries=app_context.remote.max_retries,
62
+ verify_ssl=app_context.remote.verify_ssl,
63
+ ),
64
+ }
65
+
66
+ try:
67
+ # Pass clients to the command function
68
+ return await f(*args, clients=clients, **kwargs)
69
+ finally:
70
+ # Clean up client connections
71
+ for client in clients.values():
72
+ await client.close()
73
+
74
+ return wrapper
kodit/config.py CHANGED
@@ -111,6 +111,20 @@ class PeriodicSyncConfig(BaseModel):
111
111
  )
112
112
 
113
113
 
114
+ class RemoteConfig(BaseModel):
115
+ """Configuration for remote server connection."""
116
+
117
+ server_url: str | None = Field(
118
+ default=None, description="Remote Kodit server URL"
119
+ )
120
+ api_key: str | None = Field(default=None, description="API key for authentication")
121
+ timeout: float = Field(default=30.0, description="Request timeout in seconds")
122
+ max_retries: int = Field(default=3, description="Maximum retry attempts")
123
+ verify_ssl: bool = Field(
124
+ default=True, description="Verify SSL certificates"
125
+ )
126
+
127
+
114
128
  class CustomAutoIndexingEnvSource(EnvSettingsSource):
115
129
  """Custom environment source for parsing AutoIndexingConfig."""
116
130
 
@@ -212,6 +226,9 @@ class AppContext(BaseSettings):
212
226
  default_factory=list,
213
227
  description="Comma-separated list of valid API keys (e.g. 'key1,key2')",
214
228
  )
229
+ remote: RemoteConfig = Field(
230
+ default_factory=RemoteConfig, description="Remote server configuration"
231
+ )
215
232
 
216
233
  @field_validator("api_keys", mode="before")
217
234
  @classmethod
@@ -234,6 +251,11 @@ class AppContext(BaseSettings):
234
251
  # Call this to ensure the data dir exists for the default db location
235
252
  self.get_data_dir()
236
253
 
254
+ @property
255
+ def is_remote(self) -> bool:
256
+ """Check if running in remote mode."""
257
+ return self.remote.server_url is not None
258
+
237
259
  def get_data_dir(self) -> Path:
238
260
  """Get the data directory."""
239
261
  self.data_dir.mkdir(parents=True, exist_ok=True)
@@ -248,11 +270,19 @@ class AppContext(BaseSettings):
248
270
  async def get_db(self, *, run_migrations: bool = True) -> Database:
249
271
  """Get the database."""
250
272
  if self._db is None:
251
- self._db = Database(self.db_url)
252
- if run_migrations:
253
- await self._db.run_migrations(self.db_url)
273
+ self._db = await self.new_db(run_migrations=run_migrations)
254
274
  return self._db
255
275
 
276
+ async def new_db(self, *, run_migrations: bool = True) -> Database:
277
+ """Get a completely fresh connection to a database.
278
+
279
+ This is required when running tasks in a thread pool.
280
+ """
281
+ db = Database(self.db_url)
282
+ if run_migrations:
283
+ await db.run_migrations(self.db_url)
284
+ return db
285
+
256
286
 
257
287
  with_app_context = click.make_pass_decorator(AppContext)
258
288
 
kodit/domain/entities.py CHANGED
@@ -4,16 +4,18 @@ import shutil
4
4
  from dataclasses import dataclass
5
5
  from datetime import datetime
6
6
  from pathlib import Path
7
- from typing import Protocol
7
+ from typing import Any, Protocol
8
8
  from urllib.parse import urlparse, urlunparse
9
9
 
10
10
  from pydantic import AnyUrl, BaseModel
11
11
 
12
12
  from kodit.domain.value_objects import (
13
13
  FileProcessingStatus,
14
+ QueuePriority,
14
15
  SnippetContent,
15
16
  SnippetContentType,
16
17
  SourceType,
18
+ TaskType,
17
19
  )
18
20
  from kodit.utils.path_utils import path_from_uri
19
21
 
@@ -274,3 +276,48 @@ class SnippetWithContext:
274
276
  file: File
275
277
  authors: list[Author]
276
278
  snippet: Snippet
279
+
280
+
281
+ class Task(BaseModel):
282
+ """Represents an item in the queue waiting to be processed.
283
+
284
+ If the item exists, that means it is in the queue and waiting to be processed. There
285
+ is no status associated.
286
+ """
287
+
288
+ id: str # Is a unique key to deduplicate items in the queue
289
+ type: TaskType # Task type
290
+ priority: int # Priority (higher number = higher priority)
291
+ payload: dict[str, Any] # Task-specific data
292
+
293
+ created_at: datetime | None = None # Is populated by repository
294
+ updated_at: datetime | None = None # Is populated by repository
295
+
296
+ @staticmethod
297
+ def create(task_type: TaskType, priority: int, payload: dict[str, Any]) -> "Task":
298
+ """Create a task."""
299
+ return Task(
300
+ id=Task._create_id(task_type, payload),
301
+ type=task_type,
302
+ priority=priority,
303
+ payload=payload,
304
+ )
305
+
306
+ @staticmethod
307
+ def _create_id(task_type: TaskType, payload: dict[str, Any]) -> str:
308
+ """Create a unique id for a task."""
309
+ if task_type == TaskType.INDEX_UPDATE:
310
+ return str(payload["index_id"])
311
+
312
+ raise ValueError(f"Unknown task type: {task_type}")
313
+
314
+ @staticmethod
315
+ def create_index_update_task(
316
+ index_id: int, priority: QueuePriority = QueuePriority.USER_INITIATED
317
+ ) -> "Task":
318
+ """Create an index update task."""
319
+ return Task.create(
320
+ task_type=TaskType.INDEX_UPDATE,
321
+ priority=priority.value,
322
+ payload={"index_id": index_id},
323
+ )
kodit/domain/protocols.py CHANGED
@@ -5,8 +5,35 @@ from typing import Protocol
5
5
 
6
6
  from pydantic import AnyUrl
7
7
 
8
- from kodit.domain.entities import Index, Snippet, SnippetWithContext, WorkingCopy
9
- from kodit.domain.value_objects import MultiSearchRequest
8
+ from kodit.domain.entities import Index, Snippet, SnippetWithContext, Task, WorkingCopy
9
+ from kodit.domain.value_objects import MultiSearchRequest, TaskType
10
+
11
+
12
+ class TaskRepository(Protocol):
13
+ """Repository interface for Task entities."""
14
+
15
+ async def add(
16
+ self,
17
+ task: Task,
18
+ ) -> None:
19
+ """Add a task."""
20
+ ...
21
+
22
+ async def get(self, task_id: str) -> Task | None:
23
+ """Get a task by ID."""
24
+ ...
25
+
26
+ async def take(self) -> Task | None:
27
+ """Take a task for processing."""
28
+ ...
29
+
30
+ async def update(self, task: Task) -> None:
31
+ """Update a task."""
32
+ ...
33
+
34
+ async def list(self, task_type: TaskType | None = None) -> list[Task]:
35
+ """List tasks with optional status filter."""
36
+ ...
10
37
 
11
38
 
12
39
  class IndexRepository(Protocol):
@@ -649,3 +649,16 @@ class FunctionDefinition:
649
649
  qualified_name: str
650
650
  start_byte: int
651
651
  end_byte: int
652
+
653
+
654
+ class TaskType(Enum):
655
+ """Task type."""
656
+
657
+ INDEX_UPDATE = 1
658
+
659
+
660
+ class QueuePriority(IntEnum):
661
+ """Queue priority."""
662
+
663
+ BACKGROUND = 10
664
+ USER_INITIATED = 50
@@ -0,0 +1,14 @@
1
+ """API client for remote Kodit server communication."""
2
+
3
+ from .base import BaseAPIClient
4
+ from .exceptions import AuthenticationError, KoditAPIError
5
+ from .index_client import IndexClient
6
+ from .search_client import SearchClient
7
+
8
+ __all__ = [
9
+ "AuthenticationError",
10
+ "BaseAPIClient",
11
+ "IndexClient",
12
+ "KoditAPIError",
13
+ "SearchClient",
14
+ ]
@@ -0,0 +1,100 @@
1
+ """Base HTTP client for Kodit API communication."""
2
+
3
+ import asyncio
4
+ from typing import Any
5
+
6
+ import httpx
7
+
8
+ from .exceptions import AuthenticationError, KoditAPIError
9
+
10
+
11
+ class BaseAPIClient:
12
+ """Base API client with authentication and retry logic."""
13
+
14
+ def __init__(
15
+ self,
16
+ base_url: str,
17
+ api_key: str | None = None,
18
+ timeout: float = 30.0,
19
+ max_retries: int = 3,
20
+ *,
21
+ verify_ssl: bool = True,
22
+ ) -> None:
23
+ """Initialize the API client.
24
+
25
+ Args:
26
+ base_url: Base URL of the Kodit server
27
+ api_key: API key for authentication
28
+ timeout: Request timeout in seconds
29
+ max_retries: Maximum retry attempts
30
+ verify_ssl: Whether to verify SSL certificates
31
+
32
+ """
33
+ self.base_url = base_url.rstrip("/")
34
+ self.api_key = api_key
35
+ self.timeout = timeout
36
+ self.max_retries = max_retries
37
+ self.verify_ssl = verify_ssl
38
+ self._client = self._create_client()
39
+
40
+ def _create_client(self) -> httpx.AsyncClient:
41
+ """Create the HTTP client with proper configuration."""
42
+ headers = {}
43
+ if self.api_key:
44
+ headers["X-API-Key"] = self.api_key
45
+
46
+ return httpx.AsyncClient(
47
+ base_url=self.base_url,
48
+ headers=headers,
49
+ timeout=httpx.Timeout(self.timeout),
50
+ verify=self.verify_ssl,
51
+ follow_redirects=True,
52
+ )
53
+
54
+ async def _request(
55
+ self,
56
+ method: str,
57
+ path: str,
58
+ **kwargs: Any,
59
+ ) -> httpx.Response:
60
+ """Make HTTP request with retry logic.
61
+
62
+ Args:
63
+ method: HTTP method (GET, POST, etc.)
64
+ path: API endpoint path
65
+ **kwargs: Additional arguments passed to httpx
66
+
67
+ Returns:
68
+ HTTP response object
69
+
70
+ Raises:
71
+ AuthenticationError: If authentication fails
72
+ KoditAPIError: For other API errors
73
+
74
+ """
75
+ url = f"{self.base_url}{path}"
76
+
77
+ for attempt in range(self.max_retries):
78
+ try:
79
+ response = await self._client.request(method, url, **kwargs)
80
+ response.raise_for_status()
81
+ except httpx.HTTPStatusError as e:
82
+ if e.response.status_code == 401:
83
+ raise AuthenticationError("Invalid API key") from e
84
+ if e.response.status_code >= 500 and attempt < self.max_retries - 1:
85
+ await asyncio.sleep(2**attempt) # Exponential backoff
86
+ continue
87
+ raise KoditAPIError(f"API request failed: {e}") from e
88
+ except httpx.RequestError as e:
89
+ if attempt < self.max_retries - 1:
90
+ await asyncio.sleep(2**attempt)
91
+ continue
92
+ raise KoditAPIError(f"Connection error: {e}") from e
93
+ else:
94
+ return response
95
+
96
+ raise KoditAPIError(f"Max retries ({self.max_retries}) exceeded")
97
+
98
+ async def close(self) -> None:
99
+ """Close the HTTP client."""
100
+ await self._client.aclose()
@@ -0,0 +1,21 @@
1
+ """Exceptions for Kodit API client operations."""
2
+
3
+
4
+ class KoditAPIError(Exception):
5
+ """Base exception for Kodit API errors."""
6
+
7
+
8
+
9
+ class AuthenticationError(KoditAPIError):
10
+ """Authentication failed with the API server."""
11
+
12
+
13
+
14
+ class KoditConnectionError(KoditAPIError):
15
+ """Connection to API server failed."""
16
+
17
+
18
+
19
+ class ServerError(KoditAPIError):
20
+ """Server returned an error response."""
21
+
@@ -0,0 +1,27 @@
1
+ """API endpoint constants generated from OpenAPI specification.
2
+
3
+ This file is auto-generated. Do not edit manually.
4
+ Run `make generate-api-paths` to regenerate.
5
+ """
6
+
7
+ from typing import Final
8
+
9
+
10
+ class APIEndpoints:
11
+ """API endpoint constants extracted from OpenAPI specification."""
12
+
13
+ # /api/v1/indexes
14
+ API_V1_INDEXES: Final[str] = "/api/v1/indexes"
15
+
16
+ # /api/v1/indexes/{index_id}
17
+ API_V1_INDEXES_INDEX_ID: Final[str] = "/api/v1/indexes/{index_id}"
18
+
19
+ # /api/v1/search
20
+ API_V1_SEARCH: Final[str] = "/api/v1/search"
21
+
22
+ # /healthz
23
+ HEALTHZ: Final[str] = "/healthz"
24
+
25
+
26
+ # Generated from: openapi.json
27
+ # Total endpoints: 4
@@ -0,0 +1,57 @@
1
+ """Index operations API client for Kodit server."""
2
+
3
+ from kodit.infrastructure.api.v1.schemas.index import (
4
+ IndexCreateAttributes,
5
+ IndexCreateData,
6
+ IndexCreateRequest,
7
+ IndexData,
8
+ IndexListResponse,
9
+ IndexResponse,
10
+ )
11
+
12
+ from .base import BaseAPIClient
13
+ from .exceptions import KoditAPIError
14
+ from .generated_endpoints import APIEndpoints
15
+
16
+
17
+ class IndexClient(BaseAPIClient):
18
+ """API client for index operations."""
19
+
20
+ async def list_indexes(self) -> list[IndexData]:
21
+ """List all indexes."""
22
+ response = await self._request("GET", APIEndpoints.API_V1_INDEXES)
23
+ data = IndexListResponse.model_validate_json(response.text)
24
+ return data.data
25
+
26
+ async def create_index(self, uri: str) -> IndexData:
27
+ """Create a new index."""
28
+ request = IndexCreateRequest(
29
+ data=IndexCreateData(
30
+ type="index", attributes=IndexCreateAttributes(uri=uri)
31
+ )
32
+ )
33
+ response = await self._request(
34
+ "POST", APIEndpoints.API_V1_INDEXES, json=request.model_dump()
35
+ )
36
+ result = IndexResponse.model_validate_json(response.text)
37
+ return result.data
38
+
39
+ async def get_index(self, index_id: str) -> IndexData | None:
40
+ """Get index by ID."""
41
+ try:
42
+ response = await self._request(
43
+ "GET", APIEndpoints.API_V1_INDEXES_INDEX_ID.format(index_id=index_id)
44
+ )
45
+ result = IndexResponse.model_validate_json(response.text)
46
+ except KoditAPIError as e:
47
+ if "404" in str(e):
48
+ return None
49
+ raise
50
+ else:
51
+ return result.data
52
+
53
+ async def delete_index(self, index_id: str) -> None:
54
+ """Delete an index."""
55
+ await self._request(
56
+ "DELETE", APIEndpoints.API_V1_INDEXES_INDEX_ID.format(index_id=index_id)
57
+ )
@@ -0,0 +1,86 @@
1
+ """Search operations API client for Kodit server."""
2
+
3
+ from datetime import datetime
4
+
5
+ from kodit.infrastructure.api.v1.schemas.search import (
6
+ SearchAttributes,
7
+ SearchData,
8
+ SearchFilters,
9
+ SearchRequest,
10
+ SearchResponse,
11
+ SnippetData,
12
+ )
13
+
14
+ from .base import BaseAPIClient
15
+ from .generated_endpoints import APIEndpoints
16
+
17
+
18
+ class SearchClient(BaseAPIClient):
19
+ """API client for search operations."""
20
+
21
+ async def search( # noqa: PLR0913
22
+ self,
23
+ keywords: list[str] | None = None,
24
+ code_query: str | None = None,
25
+ text_query: str | None = None,
26
+ limit: int = 10,
27
+ languages: list[str] | None = None,
28
+ authors: list[str] | None = None,
29
+ start_date: datetime | None = None,
30
+ end_date: datetime | None = None,
31
+ sources: list[str] | None = None,
32
+ file_patterns: list[str] | None = None,
33
+ ) -> list[SnippetData]:
34
+ """Search for code snippets.
35
+
36
+ Args:
37
+ keywords: Keywords to search for
38
+ code_query: Code search query
39
+ text_query: Text search query
40
+ limit: Maximum number of results
41
+ languages: Programming languages to filter by
42
+ authors: Authors to filter by
43
+ start_date: Filter snippets created after this date
44
+ end_date: Filter snippets created before this date
45
+ sources: Source repositories to filter by
46
+ file_patterns: File path patterns to filter by
47
+
48
+ Returns:
49
+ List of matching snippets
50
+
51
+ Raises:
52
+ KoditAPIError: If the request fails
53
+
54
+ """
55
+ filters = None
56
+ if any([languages, authors, start_date, end_date, sources, file_patterns]):
57
+ filters = SearchFilters(
58
+ languages=languages,
59
+ authors=authors,
60
+ start_date=start_date,
61
+ end_date=end_date,
62
+ sources=sources,
63
+ file_patterns=file_patterns,
64
+ )
65
+
66
+ request = SearchRequest(
67
+ data=SearchData(
68
+ type="search",
69
+ attributes=SearchAttributes(
70
+ keywords=keywords,
71
+ code=code_query,
72
+ text=text_query,
73
+ limit=limit,
74
+ filters=filters,
75
+ ),
76
+ )
77
+ )
78
+
79
+ response = await self._request(
80
+ "POST",
81
+ APIEndpoints.API_V1_SEARCH,
82
+ json=request.model_dump(exclude_none=True),
83
+ )
84
+
85
+ result = SearchResponse.model_validate_json(response.text)
86
+ return result.data
@@ -12,6 +12,7 @@ from kodit.application.factories.code_indexing_factory import (
12
12
  from kodit.application.services.code_indexing_application_service import (
13
13
  CodeIndexingApplicationService,
14
14
  )
15
+ from kodit.application.services.queue_service import QueueService
15
16
  from kodit.config import AppContext
16
17
  from kodit.domain.services.index_query_service import IndexQueryService
17
18
  from kodit.infrastructure.indexing.fusion_service import ReciprocalRankFusionService
@@ -68,3 +69,15 @@ async def get_indexing_app_service(
68
69
  IndexingAppServiceDep = Annotated[
69
70
  CodeIndexingApplicationService, Depends(get_indexing_app_service)
70
71
  ]
72
+
73
+
74
+ async def get_queue_service(
75
+ session: DBSessionDep,
76
+ ) -> QueueService:
77
+ """Get queue service dependency."""
78
+ return QueueService(
79
+ session=session,
80
+ )
81
+
82
+
83
+ QueueServiceDep = Annotated[QueueService, Depends(get_queue_service)]
@@ -1,11 +1,14 @@
1
1
  """Index management router for the REST API."""
2
2
 
3
- from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException
3
+ from fastapi import APIRouter, Depends, HTTPException
4
4
 
5
+ from kodit.domain.entities import Task
6
+ from kodit.domain.value_objects import QueuePriority
5
7
  from kodit.infrastructure.api.middleware.auth import api_key_auth
6
8
  from kodit.infrastructure.api.v1.dependencies import (
7
9
  IndexingAppServiceDep,
8
10
  IndexQueryServiceDep,
11
+ QueueServiceDep,
9
12
  )
10
13
  from kodit.infrastructure.api.v1.schemas.index import (
11
14
  IndexAttributes,
@@ -52,15 +55,17 @@ async def list_indexes(
52
55
  @router.post("", status_code=202)
53
56
  async def create_index(
54
57
  request: IndexCreateRequest,
55
- background_tasks: BackgroundTasks,
56
58
  app_service: IndexingAppServiceDep,
59
+ queue_service: QueueServiceDep,
57
60
  ) -> IndexResponse:
58
61
  """Create a new index and start async indexing."""
59
62
  # Create index using the application service
60
63
  index = await app_service.create_index_from_uri(request.data.attributes.uri)
61
64
 
62
- # Start async indexing in background
63
- background_tasks.add_task(app_service.run_index, index)
65
+ # Add the indexing task to the queue
66
+ await queue_service.enqueue_task(
67
+ Task.create_index_update_task(index.id, QueuePriority.USER_INITIATED)
68
+ )
64
69
 
65
70
  return IndexResponse(
66
71
  data=IndexData(
@@ -8,6 +8,7 @@ import tiktoken
8
8
 
9
9
  from kodit.domain.services.enrichment_service import EnrichmentProvider
10
10
  from kodit.domain.value_objects import EnrichmentRequest, EnrichmentResponse
11
+ from kodit.infrastructure.enrichment.utils import clean_thinking_tags
11
12
 
12
13
  ENRICHMENT_SYSTEM_PROMPT = """
13
14
  You are a professional software developer. You will be given a snippet of code.
@@ -109,7 +110,9 @@ class LocalEnrichmentProvider(EnrichmentProvider):
109
110
  content = self.tokenizer.decode(output_ids, skip_special_tokens=True).strip( # type: ignore[attr-defined]
110
111
  "\n"
111
112
  )
113
+ # Remove thinking tags from the response
114
+ cleaned_content = clean_thinking_tags(content)
112
115
  yield EnrichmentResponse(
113
116
  snippet_id=prompt["id"],
114
- text=content,
117
+ text=cleaned_content,
115
118
  )
@@ -9,6 +9,7 @@ import structlog
9
9
 
10
10
  from kodit.domain.services.enrichment_service import EnrichmentProvider
11
11
  from kodit.domain.value_objects import EnrichmentRequest, EnrichmentResponse
12
+ from kodit.infrastructure.enrichment.utils import clean_thinking_tags
12
13
 
13
14
  ENRICHMENT_SYSTEM_PROMPT = """
14
15
  You are a professional software developer. You will be given a snippet of code.
@@ -19,6 +20,7 @@ Please provide a concise explanation of the code.
19
20
  OPENAI_NUM_PARALLEL_TASKS = 40
20
21
 
21
22
 
23
+
22
24
  class OpenAIEnrichmentProvider(EnrichmentProvider):
23
25
  """OpenAI enrichment provider implementation using httpx."""
24
26
 
@@ -135,9 +137,11 @@ class OpenAIEnrichmentProvider(EnrichmentProvider):
135
137
  .get("message", {})
136
138
  .get("content", "")
137
139
  )
140
+ # Remove thinking tags from the response
141
+ cleaned_content = clean_thinking_tags(content or "")
138
142
  return EnrichmentResponse(
139
143
  snippet_id=request.snippet_id,
140
- text=content or "",
144
+ text=cleaned_content,
141
145
  )
142
146
  except Exception as e:
143
147
  self.log.exception("Error enriching request", error=str(e))