Files
gemini-cli/packages/core/src/services/memoryService.ts
T
Sandy Tao 366036a197 feat(core): introduce MemoryProvider seam and MemoryService orchestrator
Refactors the existing background skill extractor into a layered memory
subsystem that GeminiClient can drive uniformly across interactive and
non-interactive sessions.

- Adds an internal `MemoryProvider` interface (not exported, not surfaced
  via extensions) as a typing seam between the orchestrator and its
  concrete implementation
- Refactors the existing skill-extraction logic into `DefaultMemoryProvider`,
  the sole implementation of the seam
- Introduces `MemoryService` as a thin orchestrator that owns a
  `DefaultMemoryProvider` and wraps every lifecycle call in try/catch so
  a buggy provider can't crash a turn
- Moves MemoryService ownership into `GeminiClient` (private field exposed
  via optional methods) so non-interactive entry points share the same
  lifecycle as the interactive UI
- Keeps builtin memory extraction asynchronous on startup to avoid
  blocking the first turn
- Adds unit tests for MemoryService, DefaultMemoryProvider, and the
  GeminiClient integration

The whole subsystem remains gated behind the existing
`isMemoryManagerEnabled()` experimental flag.
2026-04-17 11:08:33 -07:00

86 lines
2.5 KiB
TypeScript

/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type { Config } from '../config/config.js';
import { DefaultMemoryProvider } from './defaultMemoryProvider.js';
import type { MemoryProvider } from './memoryProvider.js';
import { debugLogger } from '../utils/debugLogger.js';
/**
* Orchestrates the memory subsystem for a single GeminiClient. The service
* owns a `DefaultMemoryProvider` and exposes a stable lifecycle surface
* (`onSessionStart`, `getSystemInstructions`, `getTurnContext`,
* `onTurnComplete`, `onSessionEnd`) so that GeminiClient can stay agnostic
* of the underlying implementation. Each lifecycle call is wrapped in a
* try/catch so a buggy provider can never crash a turn.
*
* The methods return `Promise`s for callsite consistency even though the
* current provider is synchronous; this keeps the boundary stable should
* the provider ever need to do asynchronous work.
*/
export class MemoryService {
private readonly provider: MemoryProvider = new DefaultMemoryProvider();
constructor(private readonly config: Config) {}
async onSessionStart(sessionId: string): Promise<void> {
try {
this.provider.onSessionStart(this.config, sessionId);
} catch (error) {
debugLogger.warn(
`[MemoryService] Provider "${this.provider.id}" threw during onSessionStart:`,
error,
);
}
}
async getSystemInstructions(): Promise<string> {
try {
return this.provider.getSystemInstructions();
} catch (error) {
debugLogger.warn(
`[MemoryService] Provider "${this.provider.id}" threw during getSystemInstructions:`,
error,
);
return '';
}
}
async getTurnContext(query: string): Promise<string> {
try {
return this.provider.getTurnContext(query);
} catch (error) {
debugLogger.warn(
`[MemoryService] Provider "${this.provider.id}" threw during getTurnContext:`,
error,
);
return '';
}
}
onTurnComplete(userMessage: string, assistantMessage: string): void {
try {
this.provider.onTurnComplete(userMessage, assistantMessage);
} catch (error) {
debugLogger.warn(
`[MemoryService] Provider "${this.provider.id}" threw during onTurnComplete:`,
error,
);
}
}
async onSessionEnd(): Promise<void> {
try {
this.provider.onSessionEnd();
} catch (error) {
debugLogger.warn(
`[MemoryService] Provider "${this.provider.id}" threw during onSessionEnd:`,
error,
);
}
}
}