Skip to content

Feature/bifrost bridge setup - #1

Merged
codebyNorthsteep merged 13 commits into
mainfrom
feature/bifrost-bridge-setup
May 5, 2026
Merged

Feature/bifrost bridge setup#1
codebyNorthsteep merged 13 commits into
mainfrom
feature/bifrost-bridge-setup

Conversation

@codebyNorthsteep

@codebyNorthsteep codebyNorthsteep commented May 4, 2026

Copy link
Copy Markdown
Owner

This pull request introduces the core backend functionality for a Norse mythology-themed chat application, implementing REST endpoints, chat session management, integration with the OpenRouter LLM API, and personality-driven prompts. The changes establish the main domain models, service layer, storage, and configuration needed for the chat system.

Key changes include:

API Endpoints and Controller:

  • Added BifrostController to expose REST endpoints for sending chat messages, retrieving chat histories, and a welcome endpoint. The controller delegates chat logic to the service layer and logs incoming requests.

Chat Domain and Session Management:

  • Introduced ChatMessage and ChatSession domain classes to represent individual chat messages and maintain session history. [1] [2]
  • Implemented ChattSessionStorage for thread-safe, in-memory management of chat sessions, supporting retrieval and creation by session ID.

Service Layer and LLM Integration:

  • Added ChatService to handle chat logic: manages session state, builds context-aware requests for the LLM, processes responses, and stores assistant replies. Integrates with OpenRouter via a configurable RestClient.
  • Created RestClientConfiguration to configure the HTTP client with API key and base URL for OpenRouter.

Personality and Prompt Handling:

  • Defined Personality enum and PersonalityPromptProvider to generate system prompts tailored to Norse gods, influencing LLM responses according to selected personalities. [1] [2]

DTOs and Configuration:

  • Added DTOs for chat requests and LLM responses (ChatRequestDTO, OpenRouterResponseDTO). [1] [2]
  • Updated application.properties to support API key injection and HTTP client timeouts.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added REST API endpoints for sending and retrieving chat messages
    • Introduced multi-personality chatbot support with five distinct personality options
    • Implemented session-based conversation tracking and history retrieval
    • Integrated language model for generating responses

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@codebyNorthsteep has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 44 minutes and 6 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fa740e85-0386-410c-b2dd-7d1ff32cc239

📥 Commits

Reviewing files that changed from the base of the PR and between 8bcd193 and cbba9eb.

📒 Files selected for processing (1)
  • src/main/java/org/example/projectbifrost/service/ChatService.java
📝 Walkthrough

Walkthrough

Introduces a complete OpenRouter LLM chat integration service with REST endpoints, session storage, and message history management. Includes domain models, DTOs, HTTP configuration, service layer, and controller exposing /bifrost, POST /v1/chat, and GET /v1/chat/{sessionId} endpoints.

Changes

OpenRouter Chat Integration

Layer / File(s) Summary
Data Types & DTOs
src/main/java/org/example/projectbifrost/dto/ChatRequestDTO.java, src/main/java/org/example/projectbifrost/dto/Personality.java, src/main/java/org/example/projectbifrost/dto/OpenRouterRequestDTO.java, src/main/java/org/example/projectbifrost/dto/OpenRouterResponseDTO.java
Introduces ChatRequestDTO record with validation, Personality enum with five Norse-named constants and system prompts, and nested DTO records for OpenRouter request/response marshalling.
Domain Models
src/main/java/org/example/projectbifrost/domain/ChatMessage.java, src/main/java/org/example/projectbifrost/domain/ChatSession.java
Defines ChatMessage with role, content, and auto-timestamped fields; ChatSession with sessionId and mutable chatHistory list plus addMessage() method.
Infrastructure & Storage
src/main/java/org/example/projectbifrost/configuration/RestClientConfiguration.java, src/main/java/org/example/projectbifrost/storage/ChatSessionStorage.java
Configures RestClient bean with OpenRouter base URL and Bearer auth headers; provides thread-safe ChatSessionStorage using ConcurrentHashMap with lazy session creation and deletion support.
Service Logic
src/main/java/org/example/projectbifrost/service/ChatService.java
Orchestrates LLM calls by building request messages with personality system prompt and session history, POSTing to OpenRouter, parsing responses, extracting content, persisting assistant messages, and managing session retrieval.
REST Endpoints
src/main/java/org/example/projectbifrost/BifrostController.java
Exposes GET /bifrost, POST /v1/chat (logs request, delegates to service), and GET /v1/chat/{sessionId} (retrieves session history).
Configuration
src/main/resources/application.properties
Adds openrouter.api.key from environment and openrouter.model set to free tier model.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Controller as BifrostController
    participant Service as ChatService
    participant Storage as ChatSessionStorage
    participant RestClient
    participant OpenRouter

    Client->>Controller: POST /v1/chat (ChatRequestDTO)
    Controller->>Controller: Log personality, message, sessionId
    Controller->>Service: sendRequestToLLM(dto)
    
    Service->>Storage: getOrCreateChatSession(sessionId)
    Storage-->>Service: ChatSession
    
    Service->>Service: Append user message to session
    Service->>Service: Build apiMessages with system prompt + history
    Service->>RestClient: POST /chat/completions (OpenRouterRequestDTO)
    RestClient->>OpenRouter: POST request with auth headers
    OpenRouter-->>RestClient: OpenRouterResponseDTO
    RestClient-->>Service: Response body
    
    Service->>Service: Parse & validate response
    Service->>Service: Extract first choice content
    Service->>Service: Append assistant message to session
    Service-->>Controller: LLM response string
    Controller-->>Client: Response (200 OK)

    Client->>Controller: GET /v1/chat/{sessionId}
    Controller->>Service: getSessionHistory(sessionId)
    Service->>Storage: getOrCreateChatSession(sessionId)
    Storage-->>Service: ChatSession
    Service-->>Controller: ChatSession
    Controller-->>Client: ChatSession (200 OK)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hop, hop! A chat service springs to life,
With Nordic souls and OpenRouter's might,
Sessions stored safe, messages preserved bright,
Bifrost connects the mortal and the divine!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feature/bifrost bridge setup' is overly broad and uses a naming convention that mixes feature branch naming with PR title. It lacks specificity about the actual implementation (API endpoints, chat service, LLM integration) and does not clearly convey the primary changes to someone scanning the PR history. Use a more specific, descriptive title like 'Add REST API endpoints and LLM integration for chat application' or 'Implement BifrostController with OpenRouter chat service integration' to better summarize the main changes.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/bifrost-bridge-setup

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds the initial backend for a Norse-themed chat application: REST endpoints to send messages and retrieve chat history, in-memory session storage, and integration with the OpenRouter chat-completions API with personality-driven system prompts.

Changes:

  • Added REST controller endpoints for chat and session-history retrieval.
  • Implemented chat domain/session storage and a service that builds OpenRouter requests and persists conversation history.
  • Added OpenRouter RestClient configuration and basic application properties for API key + HTTP timeouts.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
src/main/resources/application.properties Adds OpenRouter API key binding and HTTP client timeout properties.
src/main/java/org/example/projectbifrost/BifrostController.java Introduces REST endpoints for welcome, chat send, and session history retrieval.
src/main/java/org/example/projectbifrost/configuration/RestClientConfiguration.java Configures a RestClient targeting OpenRouter with auth headers.
src/main/java/org/example/projectbifrost/domain/ChatMessage.java Adds message domain model with role/content/timestamp.
src/main/java/org/example/projectbifrost/domain/ChatSession.java Adds session domain model with message history and append helper.
src/main/java/org/example/projectbifrost/domain/PersonalityPromptProvider.java Maps personalities to system prompts for LLM context.
src/main/java/org/example/projectbifrost/dto/ChatRequestDTO.java Adds request DTO carrying personality, message, and sessionId.
src/main/java/org/example/projectbifrost/dto/OpenRouterResponseDTO.java Adds response DTO for parsing OpenRouter choices/messages.
src/main/java/org/example/projectbifrost/dto/Personality.java Adds supported personality enum values.
src/main/java/org/example/projectbifrost/service/ChatService.java Implements session-aware LLM request/response logic and persistence to history.
src/main/java/org/example/projectbifrost/storage/ChattSessionStorage.java Adds thread-oriented in-memory session storage component.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/main/java/org/example/projectbifrost/storage/ChattSessionStorage.java Outdated
Comment thread src/main/java/org/example/projectbifrost/storage/ChattSessionStorage.java Outdated
Comment thread src/main/java/org/example/projectbifrost/dto/ChatRequestDTO.java Outdated
Comment thread src/main/java/org/example/projectbifrost/service/ChatService.java Outdated
Comment thread src/main/java/org/example/projectbifrost/BifrostController.java
Comment thread src/main/java/org/example/projectbifrost/domain/PersonalityPromptProvider.java Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (9)
src/main/java/org/example/projectbifrost/domain/ChatMessage.java (2)

17-18: 💤 Low value

@Setter is unnecessary — ChatMessage is write-once.

ChatMessage is only ever created via its constructor and subsequently stored in session history. No code path mutates fields after construction. Removing @Setter makes the immutability intent explicit and prevents accidental mutation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/example/projectbifrost/domain/ChatMessage.java` around
lines 17 - 18, Remove the Lombok `@Setter` from the ChatMessage class to enforce
write-once semantics: delete the `@Setter` annotation (and its import if present),
mark instance fields in ChatMessage as final so they can only be set via the
existing constructor, and keep `@Getter` for read access; ensure the constructor
initializes all final fields and remove any callers/tests that rely on mutating
setters.

22-27: ⚡ Quick win

Replace LocalDateTime.now() with a timezone-aware type.

LocalDateTime carries no timezone information. If the service ever runs in multiple JVMs or regions, timestamps from different nodes become incomparable. Instant.now() (or ZonedDateTime.now(ZoneOffset.UTC)) is unambiguous.

♻️ Proposed fix
-import java.time.LocalDateTime;
+import java.time.Instant;
 
 `@Getter`
 `@Setter`
 public class ChatMessage {
     private String role;
     private String content;
-    private LocalDateTime timeStamp;
+    private Instant timeStamp;
 
     public ChatMessage(String role, String message) {
         this.role = role;
         this.content = message;
-        this.timeStamp = LocalDateTime.now();
+        this.timeStamp = Instant.now();
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/example/projectbifrost/domain/ChatMessage.java` around
lines 22 - 27, The ChatMessage class uses a LocalDateTime timeStamp set in the
constructor; replace it with a timezone-aware type (e.g., change the timeStamp
field type to java.time.Instant or java.time.ZonedDateTime) and set it using
Instant.now() (or ZonedDateTime.now(ZoneOffset.UTC)) inside the
ChatMessage(String role, String message) constructor; also update imports and
any getters/setters/serialization code that reference timeStamp to use the new
type to ensure timestamps are unambiguous across JVMs/regions.
src/main/java/org/example/projectbifrost/dto/OpenRouterRequestDTO.java (1)

6-7: ⚡ Quick win

Consider replacing List<Map<String, String>> with a dedicated message record.

List<Map<String, String>> is stringly-typed — a typo in a key ("rol" instead of "role") silently produces a malformed request to OpenRouter. A nested record enforces the contract at compile time.

♻️ Proposed refactor
 import java.util.List;
-import java.util.Map;
 
+public record OpenRouterRequestDTO(String model, List<Message> messages) {
+    public record Message(String role, String content) {}
+}
-public record OpenRouterRequestDTO(String model,
-                                   List<Map<String, String>> messages) {
-}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/example/projectbifrost/dto/OpenRouterRequestDTO.java`
around lines 6 - 7, The messages field in OpenRouterRequestDTO is currently a
List<Map<String,String>> which is error-prone; replace it with a dedicated
nested record (e.g., create record MessageDTO(String role, String content) or a
top-level MessageDTO) and change the type to List<MessageDTO> in
OpenRouterRequestDTO (constructor and accessor names remain as in the record).
Update any code constructing or serializing OpenRouterRequestDTO to build
MessageDTO instances instead of maps and adjust JSON/serialization mappings if
needed so keys are enforced as "role" and "content" at compile time.
src/main/java/org/example/projectbifrost/domain/PersonalityPromptProvider.java (1)

9-9: ⚡ Quick win

Address the TODO: move prompts into the Personality enum.

The Swedish comment flags this as a deferred refactor. Co-locating each constant's prompt directly in the enum eliminates the need for PersonalityPromptProvider entirely, reduces indirection, and ensures a new personality value can't be added without also providing its prompt.

Would you like me to generate a refactored Personality enum that embeds the prompt strings and replaces PersonalityPromptProvider?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/org/example/projectbifrost/domain/PersonalityPromptProvider.java`
at line 9, PersonalityPromptProvider is an indirection for storing prompts; move
each prompt into the Personality enum and remove PersonalityPromptProvider: add
a prompt field and accessor (e.g., private final String prompt; public String
getPrompt()) to the Personality enum and initialize it for every enum constant,
update callers to use Personality.getPrompt() instead of
PersonalityPromptProvider.getPrompt(personality), and delete the
PersonalityPromptProvider class and any unused imports.
src/main/java/org/example/projectbifrost/BifrostController.java (2)

13-13: 💤 Low value

LoggerFactory referenced via fully-qualified name rather than an import.

Logger is imported but LoggerFactory is used inline. The idiomatic pattern is to import both and use LoggerFactory.getLogger(...).

♻️ Proposed fix
 import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 ...
-    private static final Logger logger = org.slf4j.LoggerFactory.getLogger(BifrostController.class);
+    private static final Logger logger = LoggerFactory.getLogger(BifrostController.class);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/example/projectbifrost/BifrostController.java` at line 13,
Replace the fully-qualified reference to org.slf4j.LoggerFactory with an import
and use it directly: add an import for org.slf4j.LoggerFactory and update the
static logger initialization to use
LoggerFactory.getLogger(BifrostController.class) (keeping the existing Logger
type and BifrostController class name).

25-29: ⚡ Quick win

No input validation on ChatRequestDTO — missing @Valid.

Without @Valid (plus constraint annotations on the DTO), a request body with a null message or null sessionId will reach ChatService and either produce a NullPointerException or silently create malformed state. Adding @Valid with @NotBlank on the DTO fields closes this gap cheaply.

♻️ Proposed fix
-    public String sendChatRequest(`@RequestBody` ChatRequestDTO dto) {
+    public String sendChatRequest(`@Valid` `@RequestBody` ChatRequestDTO dto) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/example/projectbifrost/BifrostController.java` around lines
25 - 29, The controller currently accepts ChatRequestDTO without validation;
update sendChatRequest in BifrostController to annotate the request body with
`@Valid` (i.e., change the parameter to `@Valid` `@RequestBody` ChatRequestDTO dto)
and add bean validation annotations to the DTO (e.g., annotate message() and
sessionId() with `@NotBlank` in ChatRequestDTO; if it's a record annotate the
record components). Ensure the necessary imports (javax.validation.Valid /
jakarta.validation.Valid and javax.validation.constraints.NotBlank or jakarta
equivalents) are added and, if your app requires, add `@Validated` to the
controller class to enable method-level validation.
src/main/java/org/example/projectbifrost/storage/ChatSessionStorage.java (2)

31-34: 💤 Low value

Remove the Swedish Copilot artifact comment.

Line 33 is a raw Copilot suggestion left in production code. Either convert it to a tracked // TODO: item in English or remove it entirely.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/example/projectbifrost/storage/ChatSessionStorage.java`
around lines 31 - 34, In ChatSessionStorage.deleteSession(String sessionId),
remove the leftover Swedish Copilot comment on line 33; either delete the
artifact entirely or replace it with an English tracked TODO (e.g., "// TODO:
consider bounding sessionStorage size and evict oldest entries when exceeding
MAX_SIZE") so the method contains only valid production comments and the intent
is recorded.

21-34: No eviction policy — sessions accumulate indefinitely in memory.

sessionStorage is unbounded. A long-running deployment will accumulate a session entry per unique sessionId forever. Consider a time-based eviction strategy (e.g. Caffeine cache with expireAfterAccess) or a maximum-capacity policy; deleteSession currently has no callers so unused sessions are never reclaimed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/example/projectbifrost/storage/ChatSessionStorage.java`
around lines 21 - 34, sessionStorage is unbounded and will leak memory; replace
the ConcurrentHashMap with a bounded/time-expiring cache (e.g., Caffeine) and
update getOrCreateChatSession/deleteSession to use cache APIs: use a
Cache<String,ChatSession> with expireAfterAccess and maximumSize configured,
implement getOrCreateChatSession via cache.get(sessionId, id -> new
ChatSession(id, synchronizedList(new ArrayList<>()))), and implement
deleteSession via cache.invalidate(sessionId); add the Caffeine dependency and
configuration where this class is constructed.
src/main/java/org/example/projectbifrost/domain/ChatSession.java (1)

18-20: ⚡ Quick win

@Setter on chatHistory breaks the thread-safety contract.

setChatHistory(List<ChatMessage>) lets any caller swap in an unsynchronized ArrayList, silently discarding the Collections.synchronizedList invariant established by ChatSessionStorage. Remove @Setter (or, at minimum, guard it); @Getter alone is sufficient for the current usage.

♻️ Proposed fix
 `@Getter`
-@Setter
 `@AllArgsConstructor`
 public class ChatSession {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/example/projectbifrost/domain/ChatSession.java` around
lines 18 - 20, The `@Setter` on ChatSession allows callers to replace chatHistory
with an unsynchronized List, violating the synchronized-list invariant
established by ChatSessionStorage; remove the Lombok `@Setter` (keep `@Getter` and
`@AllArgsConstructor`) for the chatHistory field in class ChatSession, or if you
must keep a setter, make setChatHistory(List<ChatMessage>) defensive by
wrapping/copying the incoming List into Collections.synchronizedList(new
ArrayList<>(incoming)) (and assign that), ensuring chatHistory always remains a
synchronized list; update any references that relied on the generated setter
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/org/example/projectbifrost/BifrostController.java`:
- Line 27: The current logging statement in BifrostController (logger.info(...))
prints dto.message() and dto.sessionId(), which may expose PII; remove the
message body from logs and avoid including identifiable session IDs unless
allowed by policy. Update the logger.info call in BifrostController to log only
non-sensitive metadata (e.g., dto.personality()) and, if session tracking is
required, replace dto.sessionId() with a redacted/hashed value or a
boolean/opaque identifier that complies with data-handling rules; ensure
dto.message() is never logged at INFO level (or at all) and adjust any related
log messages or helper methods accordingly.

In
`@src/main/java/org/example/projectbifrost/configuration/RestClientConfiguration.java`:
- Around line 15-23: The openAIWebClient method should use the auto-configured
RestClient.Builder bean instead of RestClient.builder() so Spring Boot's
timeouts and customizers are preserved; update the RestClientConfiguration by
injecting a RestClient.Builder (e.g., add a parameter to the openAIWebClient
method or constructor) and call
builder.baseUrl(URI.create("https://openrouter.ai/api/v1")).defaultHeader("Authorization",
"Bearer " + apiKey).defaultHeader("Content-Type", "application/json").build()
using that injected Builder, leaving the bean name openAIWebClient and
preserving apiKey usage.

In `@src/main/java/org/example/projectbifrost/dto/ChatRequestDTO.java`:
- Around line 10-12: Update the ChatRequestDTO record to enforce non-empty
strings: add `@NotBlank` to the message parameter and replace `@NotNull` with
`@NotBlank` on the sessionId parameter in the ChatRequestDTO declaration, and
ensure the NotBlank import (e.g., javax.validation.constraints.NotBlank) is
present; this prevents blank messages from being sent and empty sessionId values
from bypassing validation.

In `@src/main/java/org/example/projectbifrost/service/ChatService.java`:
- Around line 73-75: getSessionHistory currently calls
ChatSessionStorage.getOrCreateChatSession which silently creates empty sessions
for unknown IDs; change storage to add a getSession(String sessionId) that
returns Optional<ChatSession>, update ChatService.getSessionHistory to call
chatSessionStorage.getSession(sessionId) and return/propagate the Optional (or
throw a NotFound-like exception), and update the controller handling the GET
/v1/chat/{sessionId} to translate an empty Optional into a 404 response instead
of returning an empty ChatSession; keep getOrCreateChatSession for the
send-message flow only.
- Around line 47-53: The iteration over chatSession.getChatHistory() is unsafe
because the list is a synchronizedList and must be iterated while holding its
monitor; fix by taking a thread-safe snapshot or locking the list: either
synchronize on the chatHistory object returned by chatSession.getChatHistory()
before calling .stream() (e.g., synchronized(chatHistory) { ... stream(...) ...
}) or create a local copy (e.g., new ArrayList<>(chatHistory)) and stream over
that when building messages in ChatService; alternatively change the storage to
use CopyOnWriteArrayList in ChatSessionStorage if append-heavy, read-safe
semantics are desired.
- Around line 57-67: The call to
restClient.post()...body(OpenRouterResponseDTO.class) can return null and
response.choices() can be empty, leading to NPE or NoSuchElementException when
you call response.choices().getFirst().message().content(); update ChatService
to defensively handle these cases: after receiving OpenRouterResponseDTO
response, check response != null, response.choices() != null and not empty (or
use Optional/stream to findFirst safely) before accessing message/content, and
if missing throw a clear RuntimeException (or custom exception) including
context (e.g., "Empty OpenRouter response or no choices") and any available
response metadata so callers don't crash unexpectedly. Ensure the null/empty
checks cover the symbols response, choices(), getFirst()/findFirst(), and
message()/content().

In `@src/main/resources/application.properties`:
- Around line 4-5: The timeout properties in application.properties won't affect
the custom RestClient built in RestClientConfiguration because it uses
RestClient.builder() directly; update RestClientConfiguration to either inject
the auto-configured RestClient.Builder from Spring (so
spring.http.client.connect-timeout and spring.http.client.read-timeout are
applied) or read those properties and apply them when constructing the
RestClient (e.g., configure the underlying HTTP client's connect/read timeouts
on the builder created in RestClientConfiguration instead of calling
RestClient.builder() with defaults). Reference RestClientConfiguration,
RestClient.builder(), and the properties spring.http.client.connect-timeout /
spring.http.client.read-timeout when making the change.

---

Nitpick comments:
In `@src/main/java/org/example/projectbifrost/BifrostController.java`:
- Line 13: Replace the fully-qualified reference to org.slf4j.LoggerFactory with
an import and use it directly: add an import for org.slf4j.LoggerFactory and
update the static logger initialization to use
LoggerFactory.getLogger(BifrostController.class) (keeping the existing Logger
type and BifrostController class name).
- Around line 25-29: The controller currently accepts ChatRequestDTO without
validation; update sendChatRequest in BifrostController to annotate the request
body with `@Valid` (i.e., change the parameter to `@Valid` `@RequestBody`
ChatRequestDTO dto) and add bean validation annotations to the DTO (e.g.,
annotate message() and sessionId() with `@NotBlank` in ChatRequestDTO; if it's a
record annotate the record components). Ensure the necessary imports
(javax.validation.Valid / jakarta.validation.Valid and
javax.validation.constraints.NotBlank or jakarta equivalents) are added and, if
your app requires, add `@Validated` to the controller class to enable method-level
validation.

In `@src/main/java/org/example/projectbifrost/domain/ChatMessage.java`:
- Around line 17-18: Remove the Lombok `@Setter` from the ChatMessage class to
enforce write-once semantics: delete the `@Setter` annotation (and its import if
present), mark instance fields in ChatMessage as final so they can only be set
via the existing constructor, and keep `@Getter` for read access; ensure the
constructor initializes all final fields and remove any callers/tests that rely
on mutating setters.
- Around line 22-27: The ChatMessage class uses a LocalDateTime timeStamp set in
the constructor; replace it with a timezone-aware type (e.g., change the
timeStamp field type to java.time.Instant or java.time.ZonedDateTime) and set it
using Instant.now() (or ZonedDateTime.now(ZoneOffset.UTC)) inside the
ChatMessage(String role, String message) constructor; also update imports and
any getters/setters/serialization code that reference timeStamp to use the new
type to ensure timestamps are unambiguous across JVMs/regions.

In `@src/main/java/org/example/projectbifrost/domain/ChatSession.java`:
- Around line 18-20: The `@Setter` on ChatSession allows callers to replace
chatHistory with an unsynchronized List, violating the synchronized-list
invariant established by ChatSessionStorage; remove the Lombok `@Setter` (keep
`@Getter` and `@AllArgsConstructor`) for the chatHistory field in class ChatSession,
or if you must keep a setter, make setChatHistory(List<ChatMessage>) defensive
by wrapping/copying the incoming List into Collections.synchronizedList(new
ArrayList<>(incoming)) (and assign that), ensuring chatHistory always remains a
synchronized list; update any references that relied on the generated setter
accordingly.

In
`@src/main/java/org/example/projectbifrost/domain/PersonalityPromptProvider.java`:
- Line 9: PersonalityPromptProvider is an indirection for storing prompts; move
each prompt into the Personality enum and remove PersonalityPromptProvider: add
a prompt field and accessor (e.g., private final String prompt; public String
getPrompt()) to the Personality enum and initialize it for every enum constant,
update callers to use Personality.getPrompt() instead of
PersonalityPromptProvider.getPrompt(personality), and delete the
PersonalityPromptProvider class and any unused imports.

In `@src/main/java/org/example/projectbifrost/dto/OpenRouterRequestDTO.java`:
- Around line 6-7: The messages field in OpenRouterRequestDTO is currently a
List<Map<String,String>> which is error-prone; replace it with a dedicated
nested record (e.g., create record MessageDTO(String role, String content) or a
top-level MessageDTO) and change the type to List<MessageDTO> in
OpenRouterRequestDTO (constructor and accessor names remain as in the record).
Update any code constructing or serializing OpenRouterRequestDTO to build
MessageDTO instances instead of maps and adjust JSON/serialization mappings if
needed so keys are enforced as "role" and "content" at compile time.

In `@src/main/java/org/example/projectbifrost/storage/ChatSessionStorage.java`:
- Around line 31-34: In ChatSessionStorage.deleteSession(String sessionId),
remove the leftover Swedish Copilot comment on line 33; either delete the
artifact entirely or replace it with an English tracked TODO (e.g., "// TODO:
consider bounding sessionStorage size and evict oldest entries when exceeding
MAX_SIZE") so the method contains only valid production comments and the intent
is recorded.
- Around line 21-34: sessionStorage is unbounded and will leak memory; replace
the ConcurrentHashMap with a bounded/time-expiring cache (e.g., Caffeine) and
update getOrCreateChatSession/deleteSession to use cache APIs: use a
Cache<String,ChatSession> with expireAfterAccess and maximumSize configured,
implement getOrCreateChatSession via cache.get(sessionId, id -> new
ChatSession(id, synchronizedList(new ArrayList<>()))), and implement
deleteSession via cache.invalidate(sessionId); add the Caffeine dependency and
configuration where this class is constructed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b2ec9e2a-c0cf-4ff8-a7f5-cec37bea2b84

📥 Commits

Reviewing files that changed from the base of the PR and between 6b5d517 and f40e24d.

📒 Files selected for processing (12)
  • src/main/java/org/example/projectbifrost/BifrostController.java
  • src/main/java/org/example/projectbifrost/configuration/RestClientConfiguration.java
  • src/main/java/org/example/projectbifrost/domain/ChatMessage.java
  • src/main/java/org/example/projectbifrost/domain/ChatSession.java
  • src/main/java/org/example/projectbifrost/domain/PersonalityPromptProvider.java
  • src/main/java/org/example/projectbifrost/dto/ChatRequestDTO.java
  • src/main/java/org/example/projectbifrost/dto/OpenRouterRequestDTO.java
  • src/main/java/org/example/projectbifrost/dto/OpenRouterResponseDTO.java
  • src/main/java/org/example/projectbifrost/dto/Personality.java
  • src/main/java/org/example/projectbifrost/service/ChatService.java
  • src/main/java/org/example/projectbifrost/storage/ChatSessionStorage.java
  • src/main/resources/application.properties


@PostMapping("/v1/chat")
public String sendChatRequest(@RequestBody ChatRequestDTO dto) {
logger.info("Received chat request: Personality={}, Message={}, SessionId={}", dto.personality(), dto.message(), dto.sessionId());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Logging user message content at INFO level is a PII/compliance risk.

dto.message() is arbitrary user-supplied chat text and must not appear in application logs — it can contain PII, credentials, or other sensitive content. dto.sessionId() can also serve as a user identifier. At minimum, omit the message body from the log line; if session tracking in logs is required, ensure it is covered by your data-handling policy.

🛡️ Proposed fix
-        logger.info("Received chat request: Personality={}, Message={}, SessionId={}", dto.personality(), dto.message(), dto.sessionId());
+        logger.info("Received chat request: Personality={}, SessionId={}", dto.personality(), dto.sessionId());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
logger.info("Received chat request: Personality={}, Message={}, SessionId={}", dto.personality(), dto.message(), dto.sessionId());
logger.info("Received chat request: Personality={}, SessionId={}", dto.personality(), dto.sessionId());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/example/projectbifrost/BifrostController.java` at line 27,
The current logging statement in BifrostController (logger.info(...)) prints
dto.message() and dto.sessionId(), which may expose PII; remove the message body
from logs and avoid including identifiable session IDs unless allowed by policy.
Update the logger.info call in BifrostController to log only non-sensitive
metadata (e.g., dto.personality()) and, if session tracking is required, replace
dto.sessionId() with a redacted/hashed value or a boolean/opaque identifier that
complies with data-handling rules; ensure dto.message() is never logged at INFO
level (or at all) and adjust any related log messages or helper methods
accordingly.

Comment thread src/main/java/org/example/projectbifrost/dto/ChatRequestDTO.java Outdated
Comment thread src/main/java/org/example/projectbifrost/service/ChatService.java Outdated
Comment thread src/main/java/org/example/projectbifrost/service/ChatService.java Outdated
Comment thread src/main/java/org/example/projectbifrost/service/ChatService.java Outdated
Comment thread src/main/resources/application.properties Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/org/example/projectbifrost/service/ChatService.java`:
- Around line 39-40: The code currently mutates session state
(chatSession.addMessage(new ChatMessage("user", dto.message())) in ChatService)
before calling the external OpenRouter API, which can leave orphaned/duplicated
turns on failure; change the flow to build the payload (apiMessages) including
the pending user input first, perform the OpenRouter /chat/completions call and
await success, then only on success append the user message and the assistant
response to the ChatSession via chatSessionStorage.getOrCreateChatSession(...)
and chatSession.addMessage(...); ensure any retries or exceptions do not mutate
session state until after the external call returns successfully.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 46b3eeac-f610-4529-8999-6d92e655984a

📥 Commits

Reviewing files that changed from the base of the PR and between f40e24d and 8bcd193.

📒 Files selected for processing (5)
  • src/main/java/org/example/projectbifrost/dto/ChatRequestDTO.java
  • src/main/java/org/example/projectbifrost/dto/OpenRouterRequestDTO.java
  • src/main/java/org/example/projectbifrost/dto/Personality.java
  • src/main/java/org/example/projectbifrost/service/ChatService.java
  • src/main/resources/application.properties
✅ Files skipped from review due to trivial changes (2)
  • src/main/java/org/example/projectbifrost/dto/Personality.java
  • src/main/resources/application.properties

Comment thread src/main/java/org/example/projectbifrost/service/ChatService.java Outdated
… handling; ensure message addition occurs after validation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants