feat: make inbound envelopes configurable
Co-authored-by: Shiva Prasad <shiv19@users.noreply.github.com>
This commit is contained in:
34
ui/src/ui/chat/message-extract.test.ts
Normal file
34
ui/src/ui/chat/message-extract.test.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { stripEnvelope } from "./message-extract";
|
||||
|
||||
describe("stripEnvelope", () => {
|
||||
it("strips UTC envelope", () => {
|
||||
const text = "[WebChat agent:main:main 2026-01-18T05:19Z] hello world";
|
||||
expect(stripEnvelope(text)).toBe("hello world");
|
||||
});
|
||||
|
||||
it("strips local-time envelope", () => {
|
||||
const text = "[Telegram Ada Lovelace (@ada) id:1234 2026-01-18 19:29 GMT+1] test";
|
||||
expect(stripEnvelope(text)).toBe("test");
|
||||
});
|
||||
|
||||
it("strips envelopes without timestamps for known channels", () => {
|
||||
const text = "[WhatsApp +1234567890] hi there";
|
||||
expect(stripEnvelope(text)).toBe("hi there");
|
||||
});
|
||||
|
||||
it("handles multi-line messages", () => {
|
||||
const text = "[Slack #general 2026-01-18T05:19Z] first line\nsecond line";
|
||||
expect(stripEnvelope(text)).toBe("first line\nsecond line");
|
||||
});
|
||||
|
||||
it("returns text as-is when no envelope present", () => {
|
||||
const text = "just a regular message";
|
||||
expect(stripEnvelope(text)).toBe("just a regular message");
|
||||
});
|
||||
|
||||
it("does not strip non-envelope brackets", () => {
|
||||
expect(stripEnvelope("[OK] hello")).toBe("[OK] hello");
|
||||
expect(stripEnvelope("[1/2] step one")).toBe("[1/2] step one");
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,42 @@
|
||||
import { stripThinkingTags } from "../format";
|
||||
|
||||
const ENVELOPE_PREFIX = /^\[([^\]]+)\]\s*/;
|
||||
const ENVELOPE_CHANNELS = [
|
||||
"WebChat",
|
||||
"WhatsApp",
|
||||
"Telegram",
|
||||
"Signal",
|
||||
"Slack",
|
||||
"Discord",
|
||||
"iMessage",
|
||||
"Teams",
|
||||
"Matrix",
|
||||
"Zalo",
|
||||
"Zalo Personal",
|
||||
"BlueBubbles",
|
||||
];
|
||||
|
||||
function looksLikeEnvelopeHeader(header: string): boolean {
|
||||
if (/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}Z\b/.test(header)) return true;
|
||||
if (/\d{4}-\d{2}-\d{2} \d{2}:\d{2}\b/.test(header)) return true;
|
||||
return ENVELOPE_CHANNELS.some((label) => header.startsWith(`${label} `));
|
||||
}
|
||||
|
||||
export function stripEnvelope(text: string): string {
|
||||
const match = text.match(ENVELOPE_PREFIX);
|
||||
if (!match) return text;
|
||||
const header = match[1] ?? "";
|
||||
if (!looksLikeEnvelopeHeader(header)) return text;
|
||||
return text.slice(match[0].length);
|
||||
}
|
||||
|
||||
export function extractText(message: unknown): string | null {
|
||||
const m = message as Record<string, unknown>;
|
||||
const role = typeof m.role === "string" ? m.role : "";
|
||||
const content = m.content;
|
||||
if (typeof content === "string") {
|
||||
return role === "assistant" ? stripThinkingTags(content) : content;
|
||||
const processed = role === "assistant" ? stripThinkingTags(content) : stripEnvelope(content);
|
||||
return processed;
|
||||
}
|
||||
if (Array.isArray(content)) {
|
||||
const parts = content
|
||||
@@ -17,11 +48,13 @@ export function extractText(message: unknown): string | null {
|
||||
.filter((v): v is string => typeof v === "string");
|
||||
if (parts.length > 0) {
|
||||
const joined = parts.join("\n");
|
||||
return role === "assistant" ? stripThinkingTags(joined) : joined;
|
||||
const processed = role === "assistant" ? stripThinkingTags(joined) : stripEnvelope(joined);
|
||||
return processed;
|
||||
}
|
||||
}
|
||||
if (typeof m.text === "string") {
|
||||
return role === "assistant" ? stripThinkingTags(m.text) : m.text;
|
||||
const processed = role === "assistant" ? stripThinkingTags(m.text) : stripEnvelope(m.text);
|
||||
return processed;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -83,4 +116,3 @@ export function formatReasoningMarkdown(text: string): string {
|
||||
.map((line) => `_${line}_`);
|
||||
return lines.length ? ["_Reasoning:_", ...lines].join("\n") : "";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { GatewayBrowserClient } from "../gateway";
|
||||
import { stripThinkingTags } from "../format";
|
||||
import { extractText } from "../chat/message-extract";
|
||||
import { generateUUID } from "../uuid";
|
||||
|
||||
export type ChatState = {
|
||||
@@ -142,29 +142,3 @@ export function handleChatEvent(
|
||||
}
|
||||
return payload.state;
|
||||
}
|
||||
|
||||
function extractText(message: unknown): string | null {
|
||||
const m = message as Record<string, unknown>;
|
||||
const role = typeof m.role === "string" ? m.role : "";
|
||||
const content = m.content;
|
||||
if (typeof content === "string") {
|
||||
return role === "assistant" ? stripThinkingTags(content) : content;
|
||||
}
|
||||
if (Array.isArray(content)) {
|
||||
const parts = content
|
||||
.map((p) => {
|
||||
const item = p as Record<string, unknown>;
|
||||
if (item.type === "text" && typeof item.text === "string") return item.text;
|
||||
return null;
|
||||
})
|
||||
.filter((v): v is string => typeof v === "string");
|
||||
if (parts.length > 0) {
|
||||
const joined = parts.join("\n");
|
||||
return role === "assistant" ? stripThinkingTags(joined) : joined;
|
||||
}
|
||||
}
|
||||
if (typeof m.text === "string") {
|
||||
return role === "assistant" ? stripThinkingTags(m.text) : m.text;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user