mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-31 22:23:09 -07:00
116 lines
3.2 KiB
TypeScript
116 lines
3.2 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2026 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* Source of an injection into the model conversation.
|
|
* - `user_steering`: Interactive guidance from the user (gated on model steering).
|
|
* - `background_completion`: Output from a backgrounded execution that has finished.
|
|
*/
|
|
|
|
import { debugLogger } from '../utils/debugLogger.js';
|
|
|
|
export type InjectionSource = 'user_steering' | 'background_completion';
|
|
|
|
/**
|
|
* Typed listener that receives both the injection text and its source.
|
|
*/
|
|
export type InjectionListener = (text: string, source: InjectionSource) => void;
|
|
|
|
/**
|
|
* Service for managing injections into the model conversation.
|
|
*
|
|
* Multiple sources (user steering, background execution completions, etc.)
|
|
* can feed into this service. Consumers register listeners via
|
|
* {@link onInjection} to receive injections with source information.
|
|
*/
|
|
export class InjectionService {
|
|
private readonly injections: Array<{
|
|
text: string;
|
|
source: InjectionSource;
|
|
timestamp: number;
|
|
}> = [];
|
|
private readonly injectionListeners: Set<InjectionListener> = new Set();
|
|
|
|
constructor(private readonly isEnabled: () => boolean) {}
|
|
|
|
/**
|
|
* Adds an injection from any source.
|
|
*
|
|
* `user_steering` injections are gated on model steering being enabled.
|
|
* Other sources (e.g. `background_completion`) are always accepted.
|
|
*/
|
|
addInjection(text: string, source: InjectionSource): void {
|
|
if (source === 'user_steering' && !this.isEnabled()) {
|
|
return;
|
|
}
|
|
const trimmed = text.trim();
|
|
if (trimmed.length === 0) {
|
|
return;
|
|
}
|
|
this.injections.push({ text: trimmed, source, timestamp: Date.now() });
|
|
|
|
for (const listener of this.injectionListeners) {
|
|
try {
|
|
listener(trimmed, source);
|
|
} catch (error) {
|
|
debugLogger.warn(
|
|
`Injection listener failed for source "${source}": ${error}`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Registers a listener for injections from any source.
|
|
*/
|
|
onInjection(listener: InjectionListener): void {
|
|
this.injectionListeners.add(listener);
|
|
}
|
|
|
|
/**
|
|
* Unregisters an injection listener.
|
|
*/
|
|
offInjection(listener: InjectionListener): void {
|
|
this.injectionListeners.delete(listener);
|
|
}
|
|
|
|
/**
|
|
* Returns collected injection texts, optionally filtered by source.
|
|
*/
|
|
getInjections(source?: InjectionSource): string[] {
|
|
const items = source
|
|
? this.injections.filter((h) => h.source === source)
|
|
: this.injections;
|
|
return items.map((h) => h.text);
|
|
}
|
|
|
|
/**
|
|
* Returns injection texts added after a specific index, optionally filtered by source.
|
|
*/
|
|
getInjectionsAfter(index: number, source?: InjectionSource): string[] {
|
|
if (index < 0) {
|
|
return this.getInjections(source);
|
|
}
|
|
const items = this.injections.slice(index + 1);
|
|
const filtered = source ? items.filter((h) => h.source === source) : items;
|
|
return filtered.map((h) => h.text);
|
|
}
|
|
|
|
/**
|
|
* Returns the index of the latest injection.
|
|
*/
|
|
getLatestInjectionIndex(): number {
|
|
return this.injections.length - 1;
|
|
}
|
|
|
|
/**
|
|
* Clears all collected injections.
|
|
*/
|
|
clear(): void {
|
|
this.injections.length = 0;
|
|
}
|
|
}
|