File size: 2,660 Bytes
68d7816 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | import { Disposable } from '#/_base/di/lifecycle';
import { LifecycleScope } from '#/app/scopes';
import { ScopeActivation, registerScopedService } from '#/_base/di/scope';
import { IAgentContextMemoryService } from '#/agent/contextMemory/contextMemory';
import type { ContextMessage } from '#/agent/contextMemory/types';
import { isVacuousContentPart } from '#/agent/contextMemory/vacuousContent';
import { TurnEnded } from '#/agent/loop/turnOps';
import { IAgentReminderService } from '#/features/reminder/reminderService';
import { IAgentStateService } from '#/agent/state/agentState';
import { IEventBus } from '#/app/event/eventBus';
import { IAgentInterruptionReminderService } from './interruptionReminder';
import { INTERRUPTION_REMINDER_VARIANT, interruptionReminderKey } from './interruptionReminderOps';
const INTERRUPTION_REMINDER = [
'The previous turn was interrupted by the user before completion;',
'any partial output shown above is incomplete.',
"The user's next message continues the conversation.",
].join(' ');
export class AgentInterruptionReminderService
extends Disposable
implements IAgentInterruptionReminderService
{
declare readonly _serviceBrand: undefined;
constructor(
@IEventBus eventBus: IEventBus,
@IAgentContextMemoryService private readonly context: IAgentContextMemoryService,
@IAgentReminderService private readonly reminder: IAgentReminderService,
@IAgentStateService agentState: IAgentStateService,
) {
super();
agentState.contributeState(interruptionReminderKey);
this._register(
eventBus.subscribe(TurnEnded, (event) => {
if (event.reason !== 'cancelled' || event.interruptReason !== 'user_cancelled') return;
const origin = lastComparableMessage(this.context.get())?.origin;
if (origin?.kind === 'injection' && origin.variant === INTERRUPTION_REMINDER_VARIANT) return;
this.reminder.notify(INTERRUPTION_REMINDER, {
variant: INTERRUPTION_REMINDER_VARIANT,
});
}),
);
}
}
function lastComparableMessage(messages: readonly ContextMessage[]): ContextMessage | undefined {
for (let index = messages.length - 1; index >= 0; index--) {
const message = messages[index]!;
if (
message.role === 'assistant' &&
message.partial === true &&
message.toolCalls.length === 0 &&
message.content.every(isVacuousContentPart)
) {
continue;
}
return message;
}
return undefined;
}
registerScopedService(
LifecycleScope.Agent,
IAgentInterruptionReminderService,
AgentInterruptionReminderService,
ScopeActivation.OnScopeCreated,
'interruptionReminder',
);
|