Skip to content

refactor(ai): optimize AI session manager and provider factory#14

Merged
ZeR020 merged 3 commits into
mainfrom
refactor/ai-providers-optimization
May 7, 2026
Merged

refactor(ai): optimize AI session manager and provider factory#14
ZeR020 merged 3 commits into
mainfrom
refactor/ai-providers-optimization

Conversation

@ZeR020

@ZeR020 ZeR020 commented May 7, 2026

Copy link
Copy Markdown
Owner

Changes

  • Prepared statement caching in AISessionManager: Added 11 cached prepared statements for all hot-path queries (getSession, getMessages, getLastSequence, addMessage, addMessageAtomic, cleanupExpired, deleteSession, clearMessages, updateConversationId, updateMetadata, updateBoth). Eliminates re-preparation overhead on every AI provider interaction.
  • Fixed this binding bug: getMessages previously used rows.map(this.rowToMessage) which loses this context in strict mode. Fixed to rows.map((row) => this.rowToMessage(row)).
  • Eliminated dynamic SQL in updateSession: Replaced string-concatenated UPDATE with 3 static prepared statements (conversation_id only, metadata only, both). Removes SQL injection risk from internal code path.
  • Session manager caching in AIProviderFactory: Added sessionManager static cache so createProvider calls getAISessionManager() once instead of 4 times per creation.

Impact

  • -90 lines, +99 lines — net +9 lines for significant performance and safety gains
  • All hot-path SQL statements cached — no re-preparation on session/message operations
  • Dynamic SQL eliminated — safer, static statements only
  • Factory singleton pattern — reduces redundant singleton lookups

Testing

  • TypeScript compilation: clean
  • All 173 tests pass

Requesting @coderabbitai full review for additional optimization opportunities in the AI provider layer and across the codebase.

Summary by CodeRabbit

  • Refactor
    • Centralized and cached session manager to reduce overhead when creating AI providers, improving performance.
    • Precompiled database operations for session and message handling to speed queries, ensure unique message sequencing, and make expired-session cleanup more reliable.
    • No public APIs or external behavior changed; compatibility preserved.

Copilot AI review requested due to automatic review settings May 7, 2026 11:41
@coderabbitai

coderabbitai Bot commented May 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

AISessionManager now precompiles and stores SQLite prepared statements on construction and reuses them across methods; AIProviderFactory caches a single AISessionManager instance and supplies it to all provider constructors.

Changes

Session Manager Optimization & Caching

Layer / File(s) Summary
Prepared Statement Fields
src/services/ai/session/ai-session-manager.ts
AISessionManager adds instance fields for prepared statements covering session queries, message operations, sequence generation, cleanup, deletes, and update variants.
Constructor Initialization & Indexing
src/services/ai/session/ai-session-manager.ts
Constructor initializes all prepared statements via this.db.prepare(...); ai_messages indexing/uniqueness changed to (ai_session_id, sequence) unique and index on (ai_session_id, role).
Session Manager Caching
src/services/ai/ai-provider-factory.ts
AIProviderFactory adds a private static sessionManager cache and getSessionManager() accessor to return a single reused AISessionManager instance.
Factory Provider Wiring
src/services/ai/ai-provider-factory.ts
createProvider passes the cached session manager to each provider constructor instead of calling getAISessionManager() repeatedly.
Query Methods
src/services/ai/session/ai-session-manager.ts
getSession, getMessages, and getLastSequence execute the corresponding prepared statements and map rows to domain types.
Mutation & Transaction Methods
src/services/ai/session/ai-session-manager.ts
createSession, updateSession (three prepared variants), deleteSession, addMessage, addMessageAtomic (transactional sequence then insert), clearMessages, and cleanupExpiredSessions now use prepared statements.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I cached the manager, stitched SQL tight,
Prepared the statements by morning light,
Sequences hop in tidy rows,
Transactions safe where the carrot grows,
A hop, a vet, performance takes flight.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: optimizing the AI session manager and provider factory through prepared statement caching and factory singleton patterns.
Description check ✅ Passed The description covers the main changes comprehensively (prepared statements, bug fixes, SQL elimination, factory caching) and reports testing results, but the standard template sections (Type of Change, Checklist, Related Issues) are not followed.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/ai-providers-optimization

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

@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/services/ai/session/ai-session-manager.ts`:
- Around line 204-217: addMessageAtomic currently reads next_seq via
getNextSeqStmt and then inserts with addMessageStmt in separate steps, which
allows races and duplicate sequence values under concurrent writers; wrap the
read-and-insert in a single DB transaction (use your DB's transaction API or a
BEGIN/COMMIT block) so the sequence read and addMessageStmt.run happen
atomically (or instead atomically increment a counter row and use its returned
value), and enforce uniqueness at the schema level by creating the unique index
idx_ai_messages_session_sequence on ai_messages(ai_session_id, sequence); update
addMessageAtomic, getNextSeqStmt usage, and any sequence counter logic to
operate inside that transaction.
🪄 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 Plus

Run ID: 95f97b56-2f3c-41b8-ba6b-92b7654b4f9d

📥 Commits

Reviewing files that changed from the base of the PR and between 6e35f4e and ca0d0ff.

📒 Files selected for processing (2)
  • src/services/ai/ai-provider-factory.ts
  • src/services/ai/session/ai-session-manager.ts

Comment thread src/services/ai/session/ai-session-manager.ts Outdated

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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@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/services/ai/session/ai-session-manager.ts`:
- Around line 117-119: Before creating the unique index
idx_ai_messages_session_sequence_unique in ai-session-manager.ts, run a
transaction that deletes duplicate rows from the ai_messages table keeping only
the row with the smallest id per (ai_session_id, sequence) pair, then create the
unique index; ensure you execute BEGIN, DELETE (using a self-join or correlated
subquery to keep min(id)), CREATE UNIQUE INDEX IF NOT EXISTS
idx_ai_messages_session_sequence_unique ON ai_messages(ai_session_id, sequence),
and COMMIT, and add error handling that performs ROLLBACK on any failure so the
DB is not left partially migrated—implement this using the same DB handle
(this.db.run/this.db.exec) in the initialization code that currently calls
CREATE UNIQUE INDEX.
🪄 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 Plus

Run ID: 977d3434-dd54-4dfa-b39a-f444d3c6a2ef

📥 Commits

Reviewing files that changed from the base of the PR and between ca0d0ff and 18ee9b3.

📒 Files selected for processing (1)
  • src/services/ai/session/ai-session-manager.ts

Comment thread src/services/ai/session/ai-session-manager.ts 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.

🧹 Nitpick comments (2)
src/services/ai/session/ai-session-manager.ts (2)

155-180: ⚡ Quick win

createSession is the only write path not using a cached prepared statement.

Every other mutation (addMessage, updateSession, deleteSession, clearMessages, cleanupExpiredSessions) was migrated to a stored Statement, but the INSERT INTO ai_sessions at line 160 still prepares ad-hoc SQL on every call, directly contradicting the PR's stated goal of eliminating re-preparation overhead.

♻️ Proposed refactor

Add a createSessionStmt field alongside the others (lines 23–34):

+  private createSessionStmt: any;

Initialize it in the constructor (after initDatabase()):

+    this.createSessionStmt = this.db.prepare(`
+      INSERT INTO ai_sessions (
+        id, provider, session_id, conversation_id,
+        metadata, created_at, updated_at, expires_at
+      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
+    `);

Then replace the inline db.run in createSession:

-    this.db.run(
-      `
-      INSERT INTO ai_sessions (
-        id, provider, session_id, conversation_id,
-        metadata, created_at, updated_at, expires_at
-      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
-    `,
-      [
-        id,
-        params.provider,
-        params.sessionId,
-        params.conversationId || null,
-        JSON.stringify(params.metadata || {}),
-        now,
-        now,
-        expiresAt,
-      ]
-    );
+    this.createSessionStmt.run(
+      id,
+      params.provider,
+      params.sessionId,
+      params.conversationId || null,
+      JSON.stringify(params.metadata || {}),
+      now,
+      now,
+      expiresAt
+    );
🤖 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/services/ai/session/ai-session-manager.ts` around lines 155 - 180,
createSession currently uses a raw db.run call instead of the prepared-statement
pattern used elsewhere; add a new Statement field createSessionStmt to the class
(matching other statement fields), initialize it in the constructor after
initDatabase() with the same INSERT INTO ai_sessions SQL, and then replace the
inline this.db.run(...) inside createSession with a call to
this.createSessionStmt.run(...) binding the same parameters (id,
params.provider, params.sessionId, params.conversationId || null,
JSON.stringify(params.metadata || {}), now, now, expiresAt), then return
getSession as before.

114-116: ⚡ Quick win

Non-unique index on (ai_session_id, sequence) is redundant after the unique index is created.

idx_ai_messages_session (line 114–116) and idx_ai_messages_session_sequence_unique (line 138) cover the exact same column pair. SQLite's query planner will use the unique index for all lookups, making the non-unique one dead weight. Maintaining two indexes on the same columns doubles the write overhead on every INSERT into ai_messages.

Since ensureUniqueMessageSequences always runs immediately after (and throws on failure), remove the non-unique index and let the unique one serve both purposes:

♻️ Proposed refactor
-    this.db.run(
-      "CREATE INDEX IF NOT EXISTS idx_ai_messages_session ON ai_messages(ai_session_id, sequence)"
-    );
-
     this.ensureUniqueMessageSequences();
🤖 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/services/ai/session/ai-session-manager.ts` around lines 114 - 116, Remove
the redundant non-unique index creation for idx_ai_messages_session on the
ai_messages table: delete the this.db.run(...) call that creates "CREATE INDEX
IF NOT EXISTS idx_ai_messages_session ON ai_messages(ai_session_id, sequence)".
Rely on the existing unique index idx_ai_messages_session_sequence_unique
(created later) to cover lookups and keep ensureUniqueMessageSequences behavior
unchanged; this avoids double write overhead on INSERTs into ai_messages and
leaves ensureUniqueMessageSequences and the unique index creation intact.
🤖 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.

Nitpick comments:
In `@src/services/ai/session/ai-session-manager.ts`:
- Around line 155-180: createSession currently uses a raw db.run call instead of
the prepared-statement pattern used elsewhere; add a new Statement field
createSessionStmt to the class (matching other statement fields), initialize it
in the constructor after initDatabase() with the same INSERT INTO ai_sessions
SQL, and then replace the inline this.db.run(...) inside createSession with a
call to this.createSessionStmt.run(...) binding the same parameters (id,
params.provider, params.sessionId, params.conversationId || null,
JSON.stringify(params.metadata || {}), now, now, expiresAt), then return
getSession as before.
- Around line 114-116: Remove the redundant non-unique index creation for
idx_ai_messages_session on the ai_messages table: delete the this.db.run(...)
call that creates "CREATE INDEX IF NOT EXISTS idx_ai_messages_session ON
ai_messages(ai_session_id, sequence)". Rely on the existing unique index
idx_ai_messages_session_sequence_unique (created later) to cover lookups and
keep ensureUniqueMessageSequences behavior unchanged; this avoids double write
overhead on INSERTs into ai_messages and leaves ensureUniqueMessageSequences and
the unique index creation intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0dc7b565-6ef4-4964-9ff3-fd3c5213e565

📥 Commits

Reviewing files that changed from the base of the PR and between 18ee9b3 and 1539dee.

📒 Files selected for processing (1)
  • src/services/ai/session/ai-session-manager.ts

@ZeR020
ZeR020 merged commit c8652aa into main May 7, 2026
5 checks passed
@ZeR020
ZeR020 deleted the refactor/ai-providers-optimization branch May 7, 2026 12:48
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