- Comprehensive API documentation - Migration examples (before/after) - Specialized manager patterns - Next steps for agent refactoring Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
256 lines
6.8 KiB
Markdown
256 lines
6.8 KiB
Markdown
# SessionManager Usage Guide
|
|
|
|
## Overview
|
|
|
|
The `SessionManager<T>` class provides a generic, reusable way to manage agent sessions with D1 database. It eliminates duplicated CRUD code across all 4 agents (Domain, Deposit, Server, Troubleshoot).
|
|
|
|
## Features
|
|
|
|
- **CRUD Operations**: get, save, delete, has
|
|
- **Expiry Management**: Automatic TTL handling
|
|
- **Message Management**: Add messages with automatic trimming (max limit)
|
|
- **Type Safety**: Full TypeScript support with generics
|
|
- **Extensible**: Specialized managers for agents with extra fields
|
|
|
|
## Basic Usage
|
|
|
|
### 1. For Simple Sessions (Deposit, Troubleshoot)
|
|
|
|
```typescript
|
|
import { SessionManager } from '../utils/session-manager';
|
|
import type { DepositSession } from '../types';
|
|
|
|
// Create manager instance
|
|
const depositSessionManager = new SessionManager<DepositSession>({
|
|
tableName: 'deposit_sessions',
|
|
ttlMs: 30 * 60 * 1000, // 30 minutes
|
|
maxMessages: 20
|
|
});
|
|
|
|
// Get or create session
|
|
let session = await depositSessionManager.get(db, userId);
|
|
if (!session) {
|
|
session = depositSessionManager.create(userId, 'collecting_amount');
|
|
}
|
|
|
|
// Add message
|
|
depositSessionManager.addMessage(session, 'user', userMessage);
|
|
|
|
// Save session
|
|
await depositSessionManager.save(db, session);
|
|
|
|
// Delete session
|
|
await depositSessionManager.delete(db, userId);
|
|
|
|
// Check if session exists
|
|
const hasSession = await depositSessionManager.has(db, userId);
|
|
```
|
|
|
|
### 2. For Sessions with Extra Fields (Domain, Server)
|
|
|
|
```typescript
|
|
import { DomainSessionManager } from '../utils/session-manager';
|
|
import type { DomainSession } from '../types';
|
|
|
|
// Use specialized manager
|
|
const domainSessionManager = new DomainSessionManager({
|
|
tableName: 'domain_sessions',
|
|
ttlMs: 60 * 60 * 1000, // 1 hour
|
|
maxMessages: 20
|
|
});
|
|
|
|
// Create with additional fields
|
|
const session = domainSessionManager.create(userId, 'gathering', {
|
|
target_domain: 'example.com'
|
|
});
|
|
|
|
// The manager handles target_domain serialization automatically
|
|
await domainSessionManager.save(db, session);
|
|
```
|
|
|
|
## Specialized Managers
|
|
|
|
### DomainSessionManager
|
|
|
|
Handles `target_domain` field for Domain Agent.
|
|
|
|
```typescript
|
|
export class DomainSessionManager extends SessionManager<DomainSession> {
|
|
protected parseAdditionalFields(result: Record<string, unknown>): Partial<DomainSession> {
|
|
return {
|
|
target_domain: result.target_domain as string | undefined,
|
|
};
|
|
}
|
|
|
|
protected getAdditionalColumns(session: DomainSession): Record<string, unknown> {
|
|
return {
|
|
target_domain: session.target_domain || null,
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
### ServerSessionManager
|
|
|
|
Handles `last_recommendation` field for Server Agent.
|
|
|
|
```typescript
|
|
export class ServerSessionManager extends SessionManager<ServerSession> {
|
|
protected parseAdditionalFields(result: Record<string, unknown>): Partial<ServerSession> {
|
|
return {
|
|
last_recommendation: result.last_recommendation
|
|
? JSON.parse(result.last_recommendation as string)
|
|
: undefined,
|
|
};
|
|
}
|
|
|
|
protected getAdditionalColumns(session: ServerSession): Record<string, unknown> {
|
|
return {
|
|
last_recommendation: session.last_recommendation
|
|
? JSON.stringify(session.last_recommendation)
|
|
: null,
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
## API Reference
|
|
|
|
### Constructor
|
|
|
|
```typescript
|
|
constructor(config: SessionManagerConfig)
|
|
```
|
|
|
|
**Parameters:**
|
|
- `config.tableName`: D1 table name (e.g., 'domain_sessions')
|
|
- `config.ttlMs`: Session TTL in milliseconds
|
|
- `config.maxMessages`: Maximum messages to keep in session
|
|
|
|
### Methods
|
|
|
|
#### get(db, userId)
|
|
Get session from D1. Returns null if not found or expired.
|
|
|
|
```typescript
|
|
async get(db: D1Database, userId: string): Promise<T | null>
|
|
```
|
|
|
|
#### save(db, session)
|
|
Save session to D1 (insert or replace). Automatically updates `updated_at` and `expires_at`.
|
|
|
|
```typescript
|
|
async save(db: D1Database, session: T): Promise<void>
|
|
```
|
|
|
|
#### delete(db, userId)
|
|
Delete session from D1.
|
|
|
|
```typescript
|
|
async delete(db: D1Database, userId: string): Promise<void>
|
|
```
|
|
|
|
#### has(db, userId)
|
|
Check if active session exists (lightweight query).
|
|
|
|
```typescript
|
|
async has(db: D1Database, userId: string): Promise<boolean>
|
|
```
|
|
|
|
#### create(userId, status, additionalFields?)
|
|
Create a new session object (not saved to DB yet).
|
|
|
|
```typescript
|
|
create(userId: string, status: string, additionalFields?: Partial<T>): T
|
|
```
|
|
|
|
#### isExpired(session)
|
|
Check if session is expired.
|
|
|
|
```typescript
|
|
isExpired(session: T): boolean
|
|
```
|
|
|
|
#### addMessage(session, role, content)
|
|
Add message to session with automatic trimming.
|
|
|
|
```typescript
|
|
addMessage(session: T, role: 'user' | 'assistant', content: string): void
|
|
```
|
|
|
|
#### getOrCreate(db, userId, initialStatus)
|
|
Get existing session or create new one (convenience method).
|
|
|
|
```typescript
|
|
async getOrCreate(db: D1Database, userId: string, initialStatus: string): Promise<T>
|
|
```
|
|
|
|
## Migration Guide
|
|
|
|
### Before (domain-agent.ts)
|
|
|
|
```typescript
|
|
// 160 lines of duplicated CRUD code
|
|
async function getDomainSession(db: D1Database, userId: string): Promise<DomainSession | null> {
|
|
// ... 40 lines
|
|
}
|
|
|
|
async function saveDomainSession(db: D1Database, session: DomainSession): Promise<void> {
|
|
// ... 30 lines
|
|
}
|
|
|
|
async function deleteDomainSession(db: D1Database, userId: string): Promise<void> {
|
|
// ... 10 lines
|
|
}
|
|
|
|
function createDomainSession(userId: string, status: DomainSessionStatus): DomainSession {
|
|
// ... 10 lines
|
|
}
|
|
|
|
function isSessionExpired(session: DomainSession): boolean {
|
|
// ... 5 lines
|
|
}
|
|
|
|
function addMessageToSession(session: DomainSession, role: 'user' | 'assistant', content: string): void {
|
|
// ... 15 lines
|
|
}
|
|
|
|
async function hasDomainSession(db: D1Database, userId: string): Promise<boolean> {
|
|
// ... 10 lines
|
|
}
|
|
```
|
|
|
|
### After (with SessionManager)
|
|
|
|
```typescript
|
|
import { DomainSessionManager } from '../utils/session-manager';
|
|
|
|
const sessionManager = new DomainSessionManager({
|
|
tableName: 'domain_sessions',
|
|
ttlMs: 60 * 60 * 1000,
|
|
maxMessages: 20
|
|
});
|
|
|
|
// All CRUD operations now use the manager
|
|
const session = await sessionManager.get(db, userId);
|
|
sessionManager.addMessage(session, 'user', message);
|
|
await sessionManager.save(db, session);
|
|
```
|
|
|
|
**Result**: ~160 lines → ~10 lines per agent = **~600 lines saved** across 4 agents!
|
|
|
|
## Next Steps
|
|
|
|
1. **Task 19**: Refactor `domain-agent.ts` to use `DomainSessionManager`
|
|
2. **Task 20**: Refactor `server-agent.ts` to use `ServerSessionManager`
|
|
3. **Task 21**: Refactor `deposit-agent.ts` to use `SessionManager<DepositSession>`
|
|
4. **Task 22**: Refactor `troubleshoot-agent.ts` to use `SessionManager<TroubleshootSession>`
|
|
|
|
## Notes
|
|
|
|
- The base `SessionManager<T>` works for simple sessions (Deposit, Troubleshoot)
|
|
- Specialized managers (`DomainSessionManager`, `ServerSessionManager`) handle extra fields
|
|
- All session operations are logged with structured logging
|
|
- Expired sessions are automatically filtered out in `get()` method
|
|
- Message trimming prevents memory bloat (configurable `maxMessages`)
|