feat: add configurable context visibility

This commit is contained in:
Peter Steinberger
2026-04-03 04:33:52 +09:00
parent d4d2d9e479
commit 35e1605147
31 changed files with 406 additions and 2 deletions

View File

@@ -0,0 +1,95 @@
import { describe, expect, it } from "vitest";
import {
evaluateSupplementalContextVisibility,
filterSupplementalContextItems,
shouldIncludeSupplementalContext,
} from "./context-visibility.js";
describe("evaluateSupplementalContextVisibility", () => {
it("reports why all mode keeps context", () => {
expect(
evaluateSupplementalContextVisibility({
mode: "all",
kind: "history",
senderAllowed: false,
}),
).toEqual({
include: true,
reason: "mode_all",
});
});
it("reports quote override decisions", () => {
expect(
evaluateSupplementalContextVisibility({
mode: "allowlist_quote",
kind: "quote",
senderAllowed: false,
}),
).toEqual({
include: true,
reason: "quote_override",
});
});
});
describe("shouldIncludeSupplementalContext", () => {
it("keeps all context in all mode", () => {
expect(
shouldIncludeSupplementalContext({
mode: "all",
kind: "history",
senderAllowed: false,
}),
).toBe(true);
});
it("enforces allowlist mode for non-allowlisted senders", () => {
expect(
shouldIncludeSupplementalContext({
mode: "allowlist",
kind: "thread",
senderAllowed: false,
}),
).toBe(false);
});
it("keeps explicit quotes in allowlist_quote mode", () => {
expect(
shouldIncludeSupplementalContext({
mode: "allowlist_quote",
kind: "quote",
senderAllowed: false,
}),
).toBe(true);
});
it("still drops non-quote context in allowlist_quote mode", () => {
expect(
shouldIncludeSupplementalContext({
mode: "allowlist_quote",
kind: "history",
senderAllowed: false,
}),
).toBe(false);
});
});
describe("filterSupplementalContextItems", () => {
it("filters blocked items and reports omission count", () => {
const result = filterSupplementalContextItems({
items: [
{ id: "allowed", senderAllowed: true },
{ id: "blocked", senderAllowed: false },
],
mode: "allowlist",
kind: "thread",
isSenderAllowed: (item) => item.senderAllowed,
});
expect(result).toEqual({
items: [{ id: "allowed", senderAllowed: true }],
omitted: 1,
});
});
});

View File

@@ -0,0 +1,58 @@
import type { ContextVisibilityMode } from "../config/types.base.js";
export type ContextVisibilityKind = "history" | "thread" | "quote" | "forwarded";
export type ContextVisibilityDecisionReason =
| "mode_all"
| "sender_allowed"
| "quote_override"
| "blocked";
export type ContextVisibilityDecision = {
include: boolean;
reason: ContextVisibilityDecisionReason;
};
export function evaluateSupplementalContextVisibility(params: {
mode: ContextVisibilityMode;
kind: ContextVisibilityKind;
senderAllowed: boolean;
}): ContextVisibilityDecision {
if (params.mode === "all") {
return { include: true, reason: "mode_all" };
}
if (params.senderAllowed) {
return { include: true, reason: "sender_allowed" };
}
if (params.mode === "allowlist_quote" && params.kind === "quote") {
return { include: true, reason: "quote_override" };
}
return { include: false, reason: "blocked" };
}
export function shouldIncludeSupplementalContext(params: {
mode: ContextVisibilityMode;
kind: ContextVisibilityKind;
senderAllowed: boolean;
}): boolean {
return evaluateSupplementalContextVisibility(params).include;
}
export function filterSupplementalContextItems<T>(params: {
items: readonly T[];
mode: ContextVisibilityMode;
kind: ContextVisibilityKind;
isSenderAllowed: (item: T) => boolean;
}): { items: T[]; omitted: number } {
const items = params.items.filter((item) =>
shouldIncludeSupplementalContext({
mode: params.mode,
kind: params.kind,
senderAllowed: params.isSenderAllowed(item),
}),
);
return {
items,
omitted: params.items.length - items.length,
};
}