refactor(auto-reply): split reply pipeline
This commit is contained in:
309
src/auto-reply/reply/get-reply-directives-apply.ts
Normal file
309
src/auto-reply/reply/get-reply-directives-apply.ts
Normal file
@@ -0,0 +1,309 @@
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import type { SessionEntry } from "../../config/sessions.js";
|
||||
import type { MsgContext } from "../templating.js";
|
||||
import type {
|
||||
ElevatedLevel,
|
||||
ReasoningLevel,
|
||||
ThinkLevel,
|
||||
VerboseLevel,
|
||||
} from "../thinking.js";
|
||||
import type { ReplyPayload } from "../types.js";
|
||||
import { buildStatusReply } from "./commands.js";
|
||||
import {
|
||||
applyInlineDirectivesFastLane,
|
||||
handleDirectiveOnly,
|
||||
type InlineDirectives,
|
||||
isDirectiveOnly,
|
||||
persistInlineDirectives,
|
||||
} from "./directive-handling.js";
|
||||
import type { createModelSelectionState } from "./model-selection.js";
|
||||
import type { TypingController } from "./typing.js";
|
||||
|
||||
type AgentDefaults = NonNullable<ClawdbotConfig["agents"]>["defaults"];
|
||||
|
||||
export type ApplyDirectiveResult =
|
||||
| { kind: "reply"; reply: ReplyPayload | ReplyPayload[] | undefined }
|
||||
| {
|
||||
kind: "continue";
|
||||
directives: InlineDirectives;
|
||||
provider: string;
|
||||
model: string;
|
||||
contextTokens: number;
|
||||
directiveAck?: ReplyPayload;
|
||||
perMessageQueueMode?: InlineDirectives["queueMode"];
|
||||
perMessageQueueOptions?: {
|
||||
debounceMs?: number;
|
||||
cap?: number;
|
||||
dropPolicy?: InlineDirectives["dropPolicy"];
|
||||
};
|
||||
};
|
||||
|
||||
export async function applyInlineDirectiveOverrides(params: {
|
||||
ctx: MsgContext;
|
||||
cfg: ClawdbotConfig;
|
||||
agentId: string;
|
||||
agentDir: string;
|
||||
agentCfg: AgentDefaults;
|
||||
sessionEntry?: SessionEntry;
|
||||
sessionStore?: Record<string, SessionEntry>;
|
||||
sessionKey: string;
|
||||
storePath?: string;
|
||||
sessionScope: Parameters<typeof buildStatusReply>[0]["sessionScope"];
|
||||
isGroup: boolean;
|
||||
allowTextCommands: boolean;
|
||||
command: Parameters<typeof buildStatusReply>[0]["command"];
|
||||
directives: InlineDirectives;
|
||||
messageProviderKey: string;
|
||||
elevatedEnabled: boolean;
|
||||
elevatedAllowed: boolean;
|
||||
elevatedFailures: Array<{ gate: string; key: string }>;
|
||||
defaultProvider: string;
|
||||
defaultModel: string;
|
||||
aliasIndex: Parameters<typeof applyInlineDirectivesFastLane>[0]["aliasIndex"];
|
||||
provider: string;
|
||||
model: string;
|
||||
modelState: Awaited<ReturnType<typeof createModelSelectionState>>;
|
||||
initialModelLabel: string;
|
||||
formatModelSwitchEvent: (label: string, alias?: string) => string;
|
||||
resolvedElevatedLevel: ElevatedLevel;
|
||||
defaultActivation: () => ReturnType<
|
||||
Parameters<typeof buildStatusReply>[0]["defaultGroupActivation"]
|
||||
>;
|
||||
contextTokens: number;
|
||||
effectiveModelDirective?: string;
|
||||
typing: TypingController;
|
||||
}): Promise<ApplyDirectiveResult> {
|
||||
const {
|
||||
ctx,
|
||||
cfg,
|
||||
agentId,
|
||||
agentDir,
|
||||
agentCfg,
|
||||
sessionEntry,
|
||||
sessionStore,
|
||||
sessionKey,
|
||||
storePath,
|
||||
sessionScope,
|
||||
isGroup,
|
||||
allowTextCommands,
|
||||
command,
|
||||
messageProviderKey,
|
||||
elevatedEnabled,
|
||||
elevatedAllowed,
|
||||
elevatedFailures,
|
||||
defaultProvider,
|
||||
defaultModel,
|
||||
aliasIndex,
|
||||
modelState,
|
||||
initialModelLabel,
|
||||
formatModelSwitchEvent,
|
||||
resolvedElevatedLevel,
|
||||
defaultActivation,
|
||||
typing,
|
||||
effectiveModelDirective,
|
||||
} = params;
|
||||
let { directives } = params;
|
||||
let { provider, model } = params;
|
||||
let { contextTokens } = params;
|
||||
|
||||
let directiveAck: ReplyPayload | undefined;
|
||||
|
||||
if (!command.isAuthorizedSender) {
|
||||
directives = {
|
||||
...directives,
|
||||
hasThinkDirective: false,
|
||||
hasVerboseDirective: false,
|
||||
hasReasoningDirective: false,
|
||||
hasElevatedDirective: false,
|
||||
hasStatusDirective: false,
|
||||
hasModelDirective: false,
|
||||
hasQueueDirective: false,
|
||||
queueReset: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
isDirectiveOnly({
|
||||
directives,
|
||||
cleanedBody: directives.cleaned,
|
||||
ctx,
|
||||
cfg,
|
||||
agentId,
|
||||
isGroup,
|
||||
})
|
||||
) {
|
||||
if (!command.isAuthorizedSender) {
|
||||
typing.cleanup();
|
||||
return { kind: "reply", reply: undefined };
|
||||
}
|
||||
const resolvedDefaultThinkLevel =
|
||||
(sessionEntry?.thinkingLevel as ThinkLevel | undefined) ??
|
||||
(agentCfg?.thinkingDefault as ThinkLevel | undefined) ??
|
||||
(await modelState.resolveDefaultThinkingLevel());
|
||||
const currentThinkLevel = resolvedDefaultThinkLevel;
|
||||
const currentVerboseLevel =
|
||||
(sessionEntry?.verboseLevel as VerboseLevel | undefined) ??
|
||||
(agentCfg?.verboseDefault as VerboseLevel | undefined);
|
||||
const currentReasoningLevel =
|
||||
(sessionEntry?.reasoningLevel as ReasoningLevel | undefined) ?? "off";
|
||||
const currentElevatedLevel =
|
||||
(sessionEntry?.elevatedLevel as ElevatedLevel | undefined) ??
|
||||
(agentCfg?.elevatedDefault as ElevatedLevel | undefined);
|
||||
const directiveReply = await handleDirectiveOnly({
|
||||
cfg,
|
||||
directives,
|
||||
sessionEntry,
|
||||
sessionStore,
|
||||
sessionKey,
|
||||
storePath,
|
||||
elevatedEnabled,
|
||||
elevatedAllowed,
|
||||
elevatedFailures,
|
||||
messageProviderKey,
|
||||
defaultProvider,
|
||||
defaultModel,
|
||||
aliasIndex,
|
||||
allowedModelKeys: modelState.allowedModelKeys,
|
||||
allowedModelCatalog: modelState.allowedModelCatalog,
|
||||
resetModelOverride: modelState.resetModelOverride,
|
||||
provider,
|
||||
model,
|
||||
initialModelLabel,
|
||||
formatModelSwitchEvent,
|
||||
currentThinkLevel,
|
||||
currentVerboseLevel,
|
||||
currentReasoningLevel,
|
||||
currentElevatedLevel,
|
||||
});
|
||||
let statusReply: ReplyPayload | undefined;
|
||||
if (
|
||||
directives.hasStatusDirective &&
|
||||
allowTextCommands &&
|
||||
command.isAuthorizedSender
|
||||
) {
|
||||
statusReply = await buildStatusReply({
|
||||
cfg,
|
||||
command,
|
||||
sessionEntry,
|
||||
sessionKey,
|
||||
sessionScope,
|
||||
provider,
|
||||
model,
|
||||
contextTokens,
|
||||
resolvedThinkLevel: resolvedDefaultThinkLevel,
|
||||
resolvedVerboseLevel: (currentVerboseLevel ?? "off") as VerboseLevel,
|
||||
resolvedReasoningLevel: (currentReasoningLevel ??
|
||||
"off") as ReasoningLevel,
|
||||
resolvedElevatedLevel,
|
||||
resolveDefaultThinkingLevel: async () => resolvedDefaultThinkLevel,
|
||||
isGroup,
|
||||
defaultGroupActivation: defaultActivation,
|
||||
});
|
||||
}
|
||||
typing.cleanup();
|
||||
if (statusReply?.text && directiveReply?.text) {
|
||||
return {
|
||||
kind: "reply",
|
||||
reply: { text: `${directiveReply.text}\n${statusReply.text}` },
|
||||
};
|
||||
}
|
||||
return { kind: "reply", reply: statusReply ?? directiveReply };
|
||||
}
|
||||
|
||||
const hasAnyDirective =
|
||||
directives.hasThinkDirective ||
|
||||
directives.hasVerboseDirective ||
|
||||
directives.hasReasoningDirective ||
|
||||
directives.hasElevatedDirective ||
|
||||
directives.hasModelDirective ||
|
||||
directives.hasQueueDirective ||
|
||||
directives.hasStatusDirective;
|
||||
|
||||
if (hasAnyDirective && command.isAuthorizedSender) {
|
||||
const fastLane = await applyInlineDirectivesFastLane({
|
||||
directives,
|
||||
commandAuthorized: command.isAuthorizedSender,
|
||||
ctx,
|
||||
cfg,
|
||||
agentId,
|
||||
isGroup,
|
||||
sessionEntry,
|
||||
sessionStore,
|
||||
sessionKey,
|
||||
storePath,
|
||||
elevatedEnabled,
|
||||
elevatedAllowed,
|
||||
elevatedFailures,
|
||||
messageProviderKey,
|
||||
defaultProvider,
|
||||
defaultModel,
|
||||
aliasIndex,
|
||||
allowedModelKeys: modelState.allowedModelKeys,
|
||||
allowedModelCatalog: modelState.allowedModelCatalog,
|
||||
resetModelOverride: modelState.resetModelOverride,
|
||||
provider,
|
||||
model,
|
||||
initialModelLabel,
|
||||
formatModelSwitchEvent,
|
||||
agentCfg,
|
||||
modelState: {
|
||||
resolveDefaultThinkingLevel: modelState.resolveDefaultThinkingLevel,
|
||||
allowedModelKeys: modelState.allowedModelKeys,
|
||||
allowedModelCatalog: modelState.allowedModelCatalog,
|
||||
resetModelOverride: modelState.resetModelOverride,
|
||||
},
|
||||
});
|
||||
directiveAck = fastLane.directiveAck;
|
||||
provider = fastLane.provider;
|
||||
model = fastLane.model;
|
||||
}
|
||||
|
||||
const persisted = await persistInlineDirectives({
|
||||
directives,
|
||||
effectiveModelDirective,
|
||||
cfg,
|
||||
agentDir,
|
||||
sessionEntry,
|
||||
sessionStore,
|
||||
sessionKey,
|
||||
storePath,
|
||||
elevatedEnabled,
|
||||
elevatedAllowed,
|
||||
defaultProvider,
|
||||
defaultModel,
|
||||
aliasIndex,
|
||||
allowedModelKeys: modelState.allowedModelKeys,
|
||||
provider,
|
||||
model,
|
||||
initialModelLabel,
|
||||
formatModelSwitchEvent,
|
||||
agentCfg,
|
||||
});
|
||||
provider = persisted.provider;
|
||||
model = persisted.model;
|
||||
contextTokens = persisted.contextTokens;
|
||||
|
||||
const perMessageQueueMode =
|
||||
directives.hasQueueDirective && !directives.queueReset
|
||||
? directives.queueMode
|
||||
: undefined;
|
||||
const perMessageQueueOptions =
|
||||
directives.hasQueueDirective && !directives.queueReset
|
||||
? {
|
||||
debounceMs: directives.debounceMs,
|
||||
cap: directives.cap,
|
||||
dropPolicy: directives.dropPolicy,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
kind: "continue",
|
||||
directives,
|
||||
provider,
|
||||
model,
|
||||
contextTokens,
|
||||
directiveAck,
|
||||
perMessageQueueMode,
|
||||
perMessageQueueOptions,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user