Files
openclaw/src/slack/stream-mode.ts
2026-02-16 23:50:42 +01:00

54 lines
1.8 KiB
TypeScript

export type SlackStreamMode = "replace" | "status_final" | "append";
const DEFAULT_STREAM_MODE: SlackStreamMode = "replace";
export function resolveSlackStreamMode(raw: unknown): SlackStreamMode {
if (typeof raw !== "string") {
return DEFAULT_STREAM_MODE;
}
const normalized = raw.trim().toLowerCase();
if (normalized === "replace" || normalized === "status_final" || normalized === "append") {
return normalized;
}
return DEFAULT_STREAM_MODE;
}
export function applyAppendOnlyStreamUpdate(params: {
incoming: string;
rendered: string;
source: string;
}): { rendered: string; source: string; changed: boolean } {
const incoming = params.incoming.trimEnd();
if (!incoming) {
return { rendered: params.rendered, source: params.source, changed: false };
}
if (!params.rendered) {
return { rendered: incoming, source: incoming, changed: true };
}
if (incoming === params.source) {
return { rendered: params.rendered, source: params.source, changed: false };
}
// Typical model partials are cumulative prefixes.
if (incoming.startsWith(params.source) || incoming.startsWith(params.rendered)) {
return { rendered: incoming, source: incoming, changed: incoming !== params.rendered };
}
// Ignore regressive shorter variants of the same stream.
if (params.source.startsWith(incoming)) {
return { rendered: params.rendered, source: params.source, changed: false };
}
const separator = params.rendered.endsWith("\n") ? "" : "\n";
return {
rendered: `${params.rendered}${separator}${incoming}`,
source: incoming,
changed: true,
};
}
export function buildStatusFinalPreviewText(updateCount: number): string {
const dots = ".".repeat((Math.max(1, updateCount) % 3) + 1);
return `Status: thinking${dots}`;
}