Files
openclaw/src/agents/pi-embedded-subscribe.handlers.types.ts
Yaroslav Boiko 838259331f fix(discord): add media dedup production code for messaging tool pipeline
Wire media URL tracking through the embedded agent pipeline so that
media already sent via messaging tools is not delivered again by the
reply dispatcher.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 23:51:51 +01:00

169 lines
5.5 KiB
TypeScript

import type { AgentEvent, AgentMessage } from "@mariozechner/pi-agent-core";
import type { ReplyDirectiveParseResult } from "../auto-reply/reply/reply-directives.js";
import type { ReasoningLevel } from "../auto-reply/thinking.js";
import type { InlineCodeState } from "../markdown/code-spans.js";
import type { HookRunner } from "../plugins/hooks.js";
import type { EmbeddedBlockChunker } from "./pi-embedded-block-chunker.js";
import type { MessagingToolSend } from "./pi-embedded-messaging.js";
import type {
BlockReplyChunking,
SubscribeEmbeddedPiSessionParams,
} from "./pi-embedded-subscribe.types.js";
import type { NormalizedUsage } from "./usage.js";
export type EmbeddedSubscribeLogger = {
debug: (message: string) => void;
warn: (message: string) => void;
};
export type ToolErrorSummary = {
toolName: string;
meta?: string;
error?: string;
mutatingAction?: boolean;
actionFingerprint?: string;
};
export type ToolCallSummary = {
meta?: string;
mutatingAction: boolean;
actionFingerprint?: string;
};
export type EmbeddedPiSubscribeState = {
assistantTexts: string[];
toolMetas: Array<{ toolName?: string; meta?: string }>;
toolMetaById: Map<string, ToolCallSummary>;
toolSummaryById: Set<string>;
lastToolError?: ToolErrorSummary;
blockReplyBreak: "text_end" | "message_end";
reasoningMode: ReasoningLevel;
includeReasoning: boolean;
shouldEmitPartialReplies: boolean;
streamReasoning: boolean;
deltaBuffer: string;
blockBuffer: string;
blockState: { thinking: boolean; final: boolean; inlineCode: InlineCodeState };
partialBlockState: { thinking: boolean; final: boolean; inlineCode: InlineCodeState };
lastStreamedAssistant?: string;
lastStreamedAssistantCleaned?: string;
emittedAssistantUpdate: boolean;
lastStreamedReasoning?: string;
lastBlockReplyText?: string;
assistantMessageIndex: number;
lastAssistantTextMessageIndex: number;
lastAssistantTextNormalized?: string;
lastAssistantTextTrimmed?: string;
assistantTextBaseline: number;
suppressBlockChunks: boolean;
lastReasoningSent?: string;
compactionInFlight: boolean;
pendingCompactionRetry: number;
compactionRetryResolve?: () => void;
compactionRetryReject?: (reason?: unknown) => void;
compactionRetryPromise: Promise<void> | null;
unsubscribed: boolean;
messagingToolSentTexts: string[];
messagingToolSentTextsNormalized: string[];
messagingToolSentTargets: MessagingToolSend[];
messagingToolSentMediaUrls: string[];
pendingMessagingTexts: Map<string, string>;
pendingMessagingTargets: Map<string, MessagingToolSend>;
successfulCronAdds: number;
pendingMessagingMediaUrls: Map<string, string>;
lastAssistant?: AgentMessage;
};
export type EmbeddedPiSubscribeContext = {
params: SubscribeEmbeddedPiSessionParams;
state: EmbeddedPiSubscribeState;
log: EmbeddedSubscribeLogger;
blockChunking?: BlockReplyChunking;
blockChunker: EmbeddedBlockChunker | null;
hookRunner?: HookRunner;
noteLastAssistant: (msg: AgentMessage) => void;
shouldEmitToolResult: () => boolean;
shouldEmitToolOutput: () => boolean;
emitToolSummary: (toolName?: string, meta?: string) => void;
emitToolOutput: (toolName?: string, meta?: string, output?: string) => void;
stripBlockTags: (
text: string,
state: { thinking: boolean; final: boolean; inlineCode?: InlineCodeState },
) => string;
emitBlockChunk: (text: string) => void;
flushBlockReplyBuffer: () => void;
emitReasoningStream: (text: string) => void;
consumeReplyDirectives: (
text: string,
options?: { final?: boolean },
) => ReplyDirectiveParseResult | null;
consumePartialReplyDirectives: (
text: string,
options?: { final?: boolean },
) => ReplyDirectiveParseResult | null;
resetAssistantMessageState: (nextAssistantTextBaseline: number) => void;
resetForCompactionRetry: () => void;
finalizeAssistantTexts: (args: {
text: string;
addedDuringMessage: boolean;
chunkerHasBuffered: boolean;
}) => void;
trimMessagingToolSent: () => void;
ensureCompactionPromise: () => void;
noteCompactionRetry: () => void;
resolveCompactionRetry: () => void;
maybeResolveCompactionWait: () => void;
recordAssistantUsage: (usage: unknown) => void;
incrementCompactionCount: () => void;
getUsageTotals: () => NormalizedUsage | undefined;
getCompactionCount: () => number;
};
/**
* Minimal context type for tool execution handlers. Allows
* tests provide only the fields they exercise
* without needing the full `EmbeddedPiSubscribeContext`.
*/
export type ToolHandlerParams = Pick<
SubscribeEmbeddedPiSessionParams,
"runId" | "onBlockReplyFlush" | "onAgentEvent" | "onToolResult"
>;
export type ToolHandlerState = Pick<
EmbeddedPiSubscribeState,
| "toolMetaById"
| "toolMetas"
| "toolSummaryById"
| "lastToolError"
| "pendingMessagingTargets"
| "pendingMessagingTexts"
| "pendingMessagingMediaUrls"
| "messagingToolSentTexts"
| "messagingToolSentTextsNormalized"
| "messagingToolSentMediaUrls"
| "messagingToolSentTargets"
>;
export type ToolHandlerContext = {
params: ToolHandlerParams;
state: ToolHandlerState;
log: EmbeddedSubscribeLogger;
hookRunner?: HookRunner;
flushBlockReplyBuffer: () => void;
shouldEmitToolResult: () => boolean;
shouldEmitToolOutput: () => boolean;
emitToolSummary: (toolName?: string, meta?: string) => void;
emitToolOutput: (toolName?: string, meta?: string, output?: string) => void;
trimMessagingToolSent: () => void;
};
export type EmbeddedPiSubscribeEvent =
| AgentEvent
| { type: string; [k: string]: unknown }
| { type: "message_start"; message: AgentMessage };