feat(M5-004,M5-005,M5-006,M5-007): session-conversation binding, session:info broadcast, agent creation from TUI, and session metrics (#321)
Some checks failed
ci/woodpecker/push/ci Pipeline failed
Some checks failed
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #321.
This commit is contained in:
@@ -23,7 +23,7 @@ import { createFileTools } from './tools/file-tools.js';
|
||||
import { createGitTools } from './tools/git-tools.js';
|
||||
import { createShellTools } from './tools/shell-tools.js';
|
||||
import { createWebTools } from './tools/web-tools.js';
|
||||
import type { SessionInfoDto } from './session.dto.js';
|
||||
import type { SessionInfoDto, SessionMetrics } from './session.dto.js';
|
||||
import { SystemOverrideService } from '../preferences/system-override.service.js';
|
||||
import { PreferencesService } from '../preferences/preferences.service.js';
|
||||
import { SessionGCService } from '../gc/session-gc.service.js';
|
||||
@@ -93,6 +93,12 @@ export interface AgentSession {
|
||||
allowedTools: string[] | null;
|
||||
/** User ID that owns this session, used for preference lookups. */
|
||||
userId?: string;
|
||||
/** Agent config ID applied to this session, if any (M5-001). */
|
||||
agentConfigId?: string;
|
||||
/** Human-readable agent name applied to this session, if any (M5-001). */
|
||||
agentName?: string;
|
||||
/** M5-007: per-session metrics. */
|
||||
metrics: SessionMetrics;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@@ -184,11 +190,13 @@ export class AgentService implements OnModuleDestroy {
|
||||
sessionId: string,
|
||||
options?: AgentSessionOptions,
|
||||
): Promise<AgentSession> {
|
||||
// Merge DB agent config when agentConfigId is provided
|
||||
// Merge DB agent config when agentConfigId is provided (M5-001)
|
||||
let mergedOptions = options;
|
||||
let resolvedAgentName: string | undefined;
|
||||
if (options?.agentConfigId) {
|
||||
const agentConfig = await this.brain.agents.findById(options.agentConfigId);
|
||||
if (agentConfig) {
|
||||
resolvedAgentName = agentConfig.name;
|
||||
mergedOptions = {
|
||||
provider: options.provider ?? agentConfig.provider,
|
||||
modelId: options.modelId ?? agentConfig.model,
|
||||
@@ -197,6 +205,8 @@ export class AgentService implements OnModuleDestroy {
|
||||
sandboxDir: options.sandboxDir,
|
||||
isAdmin: options.isAdmin,
|
||||
agentConfigId: options.agentConfigId,
|
||||
userId: options.userId,
|
||||
conversationHistory: options.conversationHistory,
|
||||
};
|
||||
this.logger.log(
|
||||
`Merged agent config "${agentConfig.name}" (${agentConfig.id}) into session ${sessionId}`,
|
||||
@@ -330,6 +340,14 @@ export class AgentService implements OnModuleDestroy {
|
||||
sandboxDir,
|
||||
allowedTools,
|
||||
userId: mergedOptions?.userId,
|
||||
agentConfigId: mergedOptions?.agentConfigId,
|
||||
agentName: resolvedAgentName,
|
||||
metrics: {
|
||||
tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
||||
modelSwitches: 0,
|
||||
messageCount: 0,
|
||||
lastActivityAt: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
|
||||
this.sessions.set(sessionId, session);
|
||||
@@ -458,10 +476,12 @@ export class AgentService implements OnModuleDestroy {
|
||||
id: s.id,
|
||||
provider: s.provider,
|
||||
modelId: s.modelId,
|
||||
...(s.agentName ? { agentName: s.agentName } : {}),
|
||||
createdAt: new Date(s.createdAt).toISOString(),
|
||||
promptCount: s.promptCount,
|
||||
channels: Array.from(s.channels),
|
||||
durationMs: now - s.createdAt,
|
||||
metrics: { ...s.metrics },
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -472,13 +492,93 @@ export class AgentService implements OnModuleDestroy {
|
||||
id: s.id,
|
||||
provider: s.provider,
|
||||
modelId: s.modelId,
|
||||
...(s.agentName ? { agentName: s.agentName } : {}),
|
||||
createdAt: new Date(s.createdAt).toISOString(),
|
||||
promptCount: s.promptCount,
|
||||
channels: Array.from(s.channels),
|
||||
durationMs: Date.now() - s.createdAt,
|
||||
metrics: { ...s.metrics },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Record token usage for a session turn (M5-007).
|
||||
* Accumulates tokens across the session lifetime.
|
||||
*/
|
||||
recordTokenUsage(
|
||||
sessionId: string,
|
||||
tokens: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number },
|
||||
): void {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (!session) return;
|
||||
session.metrics.tokens.input += tokens.input;
|
||||
session.metrics.tokens.output += tokens.output;
|
||||
session.metrics.tokens.cacheRead += tokens.cacheRead;
|
||||
session.metrics.tokens.cacheWrite += tokens.cacheWrite;
|
||||
session.metrics.tokens.total += tokens.total;
|
||||
session.metrics.lastActivityAt = new Date().toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a model switch event for a session (M5-007).
|
||||
*/
|
||||
recordModelSwitch(sessionId: string): void {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (!session) return;
|
||||
session.metrics.modelSwitches += 1;
|
||||
session.metrics.lastActivityAt = new Date().toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment message count for a session (M5-007).
|
||||
*/
|
||||
recordMessage(sessionId: string): void {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (!session) return;
|
||||
session.metrics.messageCount += 1;
|
||||
session.metrics.lastActivityAt = new Date().toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the model tracked on a live session (M5-002).
|
||||
* This records the model change in the session metadata so subsequent
|
||||
* session:info emissions reflect the new model. The Pi session itself is
|
||||
* not reconstructed — the model is used on the next createSession call for
|
||||
* the same conversationId when the session is torn down or a new one is created.
|
||||
*/
|
||||
updateSessionModel(sessionId: string, modelId: string): void {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (!session) return;
|
||||
const prev = session.modelId;
|
||||
session.modelId = modelId;
|
||||
this.recordModelSwitch(sessionId);
|
||||
this.logger.log(`Session ${sessionId}: model updated ${prev} → ${modelId} (M5-002)`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a new agent config to a live session mid-conversation (M5-003).
|
||||
* Updates agentName, agentConfigId, and modelId on the session object.
|
||||
* System prompt and tools take effect when the next session is created for
|
||||
* this conversationId (they are baked in at session creation time).
|
||||
*/
|
||||
applyAgentConfig(
|
||||
sessionId: string,
|
||||
agentConfigId: string,
|
||||
agentName: string,
|
||||
modelId?: string,
|
||||
): void {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (!session) return;
|
||||
session.agentConfigId = agentConfigId;
|
||||
session.agentName = agentName;
|
||||
if (modelId) {
|
||||
this.updateSessionModel(sessionId, modelId);
|
||||
}
|
||||
this.logger.log(
|
||||
`Session ${sessionId}: agent switched to "${agentName}" (${agentConfigId}) (M5-003)`,
|
||||
);
|
||||
}
|
||||
|
||||
addChannel(sessionId: string, channel: string): void {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (session) {
|
||||
|
||||
@@ -1,11 +1,32 @@
|
||||
/** Token usage metrics for a session (M5-007). */
|
||||
export interface SessionTokenMetrics {
|
||||
input: number;
|
||||
output: number;
|
||||
cacheRead: number;
|
||||
cacheWrite: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
/** Per-session metrics tracked throughout the session lifetime (M5-007). */
|
||||
export interface SessionMetrics {
|
||||
tokens: SessionTokenMetrics;
|
||||
modelSwitches: number;
|
||||
messageCount: number;
|
||||
lastActivityAt: string;
|
||||
}
|
||||
|
||||
export interface SessionInfoDto {
|
||||
id: string;
|
||||
provider: string;
|
||||
modelId: string;
|
||||
/** M5-005: human-readable agent name when an agent config is applied. */
|
||||
agentName?: string;
|
||||
createdAt: string;
|
||||
promptCount: number;
|
||||
channels: string[];
|
||||
durationMs: number;
|
||||
/** M5-007: per-session metrics (token usage, model switches, etc.) */
|
||||
metrics: SessionMetrics;
|
||||
}
|
||||
|
||||
export interface SessionListDto {
|
||||
|
||||
Reference in New Issue
Block a user