mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
* chore(config): ignore additional agent workflow command files Add newly introduced agent workflow and Claude command files to .gitignore so proprietary automation assets are not committed. * feat(deepseek-web): fix auth to use userToken + WASM PoW solver Rewrite deepseek-web executor from broken cookie auth to userToken Bearer flow (like Chat2API). Replace pure JS Keccak PoW with WASM solver (5.8s → 86ms). Add 14 models, validation, and dashboard UX. * fix(deepseek-web): update target_path to use challenge property * refactor(deepseek-web): streamline token handling and implement cache eviction * fix(deepseek-web): fix SSE parser, prompt format, and error handling - Handle all 3 DeepSeek SSE stream formats: initial fragments, APPEND operations, and bare string tokens (fixes truncated responses) - Simplify prompt builder to send system + last user message only (DeepSeek web API is single-turn, full history caused marker leakage) - Check json.code before token extraction (fixes "did not return access token: Authorization" on code 40003 with HTTP 200) - Clear session cache alongside token cache on auth errors - Add dev origin for remote testing Co-authored-by: Cursor <cursoragent@cursor.com> * chore: ignore memory-bank and cursor agent rules from tracking Co-authored-by: Cursor <cursoragent@cursor.com> * feat: enhance documentation and configuration for Fumadocs integration - Added Fumadocs MDX support in the Next.js configuration. - Updated transpile packages to include fumadocs-ui and fumadocs-core. - Implemented a comprehensive set of redirects for documentation paths to improve navigation. - Removed the generate-docs-index script as it is no longer needed. - Updated various documentation titles for consistency and clarity. - Enhanced global styles to incorporate Fumadocs UI themes and styles. * refactor(docs): cleanup fumadocs PR — revert deepseek, add i18n fallback, restore LanguageSelector - Revert unrelated deepseek-web.ts changes (should be separate PR) - Add .source/ to .gitignore (Fumadocs generated files) - Remove contributor IP from allowedDevOrigins - Add i18n runtime fallback: reads NEXT_LOCALE cookie, loads translated .md from docs/i18n/<locale>/docs/ (preserves existing translation pipeline) - Restore LanguageSelector in Fumadocs layout nav - Restore SEO metadata (title template, description, robots) * fix(codex): use allowlist to strip non-Responses-API fields in non-passthrough path (#2608) (#2615) Integrated into release/v3.8.3 — fix(codex): allowlist-based sanitization for gpt-5.5 Responses API * fix(deepseek-web): fix SSE parser, prompt format, error handling, and cache keys (#2616) Integrated into release/v3.8.3 — fix(deepseek-web): SSE parser (APPEND + bare tokens), prompt builder, error handling, session cache cleanup * chore(config): ignore additional agent workflow command files Add newly introduced agent workflow and Claude command files to .gitignore so proprietary automation assets are not committed. * feat(docs): migrate /docs to Fumadocs MDX with nested routes (#2614) Integrated into release/v3.8.3 — Fumadocs MDX migration with nested routes, search API, and 50+ URL redirects * fix(catalog): skip static PROVIDER_MODELS when synced models exist (#2625) Integrated into release/v3.8.3 * fix(qoder): Cosy auth fallback for PAT tokens + vision support for qwen3-vl-plus (#2629) Integrated into release/v3.8.3 * fix(cli): register tsx loader and add opencode config subcommand (#2631) Integrated into release/v3.8.3 * feat(dashboard): add search and filters to /dashboard/api-manager (#2628) Integrated into release/v3.8.3 * fix(claude): improve Pi and OpenCode compatibility (#2621) Integrated into release/v3.8.3 * fix: restore semantic passthrough system-role-only extraction instead of full normalization (#2620) Integrated into release/v3.8.3 * fix(kiro): stabilize conversationId across prompt compression (#2630) Integrated into release/v3.8.3 * fix(deepseek-web): SSE thinking/search routing and session lifecycle (#2624) Integrated into release/v3.8.3 — DeepSeek Web SSE thinking/search routing overhaul * feat(dashboard): free-tier grouping with symbolic link in /providers (#2632) Integrated into release/v3.8.3 * fix: close implementation gaps — t3-chat-web, stream_options, combo_strategy, batch config (#2634) Integrated into release/v3.8.3 * feat(dashboard): risk notice modal for sensitive providers (#2633) Integrated into release/v3.8.3 * fix(reasoning): extend reasoning_content injection to Kimi K2 and other replay models (#2639) Integrated into release/v3.8.3 * fix(cli): Linux autostart via systemd user service (fixes #2627) (#2635) Integrated into release/v3.8.3 * Refactor/providers free tier (#2640) Integrated into release/v3.8.3 * fix(tests): remove duplicate assertion in schema coercion & fix(cli): ignore system vars in env check * fix(combo): preserve omniModel tag in streaming output for round-trip context pinning (#2646) Integrated into release/v3.8.3 * feat(dashboard): media providers pages + Web Fetch category (#2645) Integrated into release/v3.8.3 * Feature provider adapta org com tutorial de conexão em modal (#2643) Integrated into release/v3.8.3 * fix(rtk): skip content-based filter matching for non-shell tool results (#2642) Integrated into release/v3.8.3 * fix(translator): enable Claude extended thinking for Copilot Responses-API requests (#2647) Integrated into release/v3.8.3 * feat(dashboard): add search and filters to /dashboard/api-manager (#2641) Integrated into release/v3.8.3 * feat(dashboard): risk notice modal for sensitive providers (#2638) Integrated into release/v3.8.3 * feat(dashboard): mini-playground inline (Phase 4) (#2648) Integrated into release/v3.8.3 * fix(settings): fix Require Login modal Cancel button text and dismissal (#2649) Integrated into release/v3.8.3 * feat(combos): universal context handoff for cross-model conversation continuity (#2653) Integrated into release/v3.8.3 * chore(release): bump to v3.8.3 — changelog, docs, version sync * feat(i18n): complete zh-CN translations for 1220 missing keys (#2655) Integrated into release/v3.8.3 * chore(release): include electron package changes in v3.8.3 * docs(changelog): integrate PR #2655 into v3.8.3 * feat(i18n): translate 377 additional zh-CN entries (81 new keys + 296 same-as-en) (#2659) Integrated into release/v3.8.3 * feat(dashboard): add Cmd+K / Ctrl+K command palette for sidebar navigation (#2656) Integrated into release/v3.8.3 * docs: update changelog for PR integrations under v3.8.3 * feat(cli): integrate native updates, autostart and headless CLI mode (#2662) Integrated into release/v3.8.3 * fix(proxy): save dashboard custom proxies in registry (#2661) Integrated into release/v3.8.3 * feat(dashboard): chat-first test slide-over (Option A) (#2660) Integrated into release/v3.8.3 * docs: update changelog with Batch 2 PR merges for v3.8.3 * fix: add xhigh+max to effortLevel schema; add opencode-plugin publish job (#2666) Integrated into release/v3.8.3 * docs: update changelog with Batch 3 PR #2666 merge for v3.8.3 * feat(quota+providers): card-grid layout, provider group headers, Codex race fix (#2667) Integrated into release/v3.8.3 * feat(dashboard): real-time live WebSocket monitoring (#2668) Integrated into release/v3.8.3 * feat(copilot): AI assistant with CodeGraph + CLI + knowledge base (#2669) Integrated into release/v3.8.3 * feat(pipeline): pre-request middleware hooks (#2670) Integrated into release/v3.8.3 * feat(resilience): credential health check + adaptive circuit breaker (#2671) Integrated into release/v3.8.3 * feat(playground): combo routing visual simulator (#2672) Integrated into release/v3.8.3 * feat(auth): API key groups with model-level permissions (#2673) Integrated into release/v3.8.3 * feat(pwa): enhanced manifest + push notification support (#2674) Integrated into release/v3.8.3 * feat(proxy): serverless relay endpoints with rate limiting (#2675) Integrated into release/v3.8.3 * docs(changelog): update changelog for PRs 2667-2675 & fix: resolve typescript compile-time errors * fix(db): remove transactions from migrations Remove explicit transaction wrappers from recent migrations and correct the API key groups migration metadata. Also fix codegraph path resolution for ESM environments and refresh generated fumadocs source output. --------- Co-authored-by: Ömer Vehbe <ovehbe@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Mr. Meowgi <mr@meowgi.dev> Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com> Co-authored-by: amogus22877769 <y.lev357@gmail.com> Co-authored-by: Halil Tezcan KARABULUT <info@hlltzcnkb.com> Co-authored-by: Tentoxa <53821604+Tentoxa@users.noreply.github.com> Co-authored-by: HALDRO <121296348+HALDRO@users.noreply.github.com> Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com> Co-authored-by: janeza2 <49841619+janeza2@users.noreply.github.com> Co-authored-by: df4p <38404+df4p@users.noreply.github.com> Co-authored-by: ivan-mezentsev <ivan@mezentsev.me> Co-authored-by: Chewji <126886556+Chewji9875@users.noreply.github.com> Co-authored-by: L-aros <107354918+L-aros@users.noreply.github.com> Co-authored-by: M.M <mr.maatoug@gmail.com> Co-authored-by: Benson K B <bensonkbmca@gmail.com> Co-authored-by: terence71-glitch <mcdowellterence71@gmail.com>
556 lines
23 KiB
TypeScript
556 lines
23 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const {
|
|
applySystemTransformPipeline,
|
|
applyTransformPipeline,
|
|
setSystemTransformsConfig,
|
|
resetSystemTransformsConfig,
|
|
getSystemTransformsConfig,
|
|
DEFAULT_SYSTEM_TRANSFORMS_CONFIG,
|
|
DEFAULT_CLAUDE_PIPELINE,
|
|
DEFAULT_CC_BRIDGE_PROVIDER_PIPELINE,
|
|
DEFAULT_OBFUSCATE_WORDS,
|
|
OPENWEBUI_PARAGRAPH_ANCHORS,
|
|
OPENWEBUI_IDENTITY_PREFIXES,
|
|
PI_PARAGRAPH_ANCHORS,
|
|
PROVIDER_CLAUDE,
|
|
PROVIDER_CC_BRIDGE,
|
|
} = await import("../../open-sse/services/systemTransforms.ts");
|
|
|
|
const ZWJ = "\u200d";
|
|
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
// Defaults
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
|
|
test("defaults: PROVIDER_CLAUDE and PROVIDER_CC_BRIDGE keys are present", () => {
|
|
assert.equal(PROVIDER_CLAUDE, "claude");
|
|
assert.equal(PROVIDER_CC_BRIDGE, "anthropic-compatible-cc");
|
|
assert.ok(DEFAULT_SYSTEM_TRANSFORMS_CONFIG.providers[PROVIDER_CLAUDE]);
|
|
assert.ok(DEFAULT_SYSTEM_TRANSFORMS_CONFIG.providers[PROVIDER_CC_BRIDGE]);
|
|
});
|
|
|
|
test("defaults: claude pipeline omits inject_billing_header", () => {
|
|
const kinds = DEFAULT_CLAUDE_PIPELINE.map((op: { kind: string }) => op.kind);
|
|
assert.ok(!kinds.includes("inject_billing_header"));
|
|
// It does include obfuscate_words and paragraph drops.
|
|
assert.ok(kinds.includes("obfuscate_words"));
|
|
assert.ok(kinds.includes("drop_paragraph_if_contains"));
|
|
});
|
|
|
|
test("defaults: CC bridge provider pipeline keeps inject_billing_header", () => {
|
|
const kinds = DEFAULT_CC_BRIDGE_PROVIDER_PIPELINE.map((op: { kind: string }) => op.kind);
|
|
assert.ok(kinds.includes("inject_billing_header"));
|
|
assert.ok(kinds.includes("prepend_system_block"));
|
|
// And layers Open WebUI obfuscation on top.
|
|
assert.ok(kinds.includes("obfuscate_words"));
|
|
});
|
|
|
|
test("defaults: obfuscate_words list includes legacy + OpenWebUI words", () => {
|
|
assert.ok(DEFAULT_OBFUSCATE_WORDS.includes("opencode"));
|
|
assert.ok(DEFAULT_OBFUSCATE_WORDS.includes("cline"));
|
|
assert.ok(DEFAULT_OBFUSCATE_WORDS.includes("openwebui"));
|
|
assert.ok(DEFAULT_OBFUSCATE_WORDS.includes("open-webui"));
|
|
});
|
|
|
|
test("defaults: OpenWebUI anchors include canonical URLs", () => {
|
|
assert.ok(OPENWEBUI_PARAGRAPH_ANCHORS.includes("github.com/open-webui/open-webui"));
|
|
assert.ok(OPENWEBUI_PARAGRAPH_ANCHORS.includes("openwebui.com"));
|
|
assert.ok(OPENWEBUI_IDENTITY_PREFIXES.includes("You are Open WebUI"));
|
|
});
|
|
|
|
test("defaults: Pi anchors include package documentation paths", () => {
|
|
assert.ok(PI_PARAGRAPH_ANCHORS.includes("@earendil-works/pi-coding-agent"));
|
|
});
|
|
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
// obfuscate_words op
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
|
|
test("obfuscate_words inserts ZWJ in system text blocks", () => {
|
|
const body = {
|
|
system: [{ type: "text", text: "Built on opencode framework." }],
|
|
messages: [{ role: "user", content: "hi" }],
|
|
};
|
|
applyTransformPipeline(body, [
|
|
{ kind: "obfuscate_words", words: ["opencode"], targets: ["system"] },
|
|
]);
|
|
const text = (body.system[0] as { text: string }).text;
|
|
assert.ok(text.includes(`o${ZWJ}pencode`));
|
|
assert.ok(!text.includes(" opencode ") || text.includes(`o${ZWJ}pencode`));
|
|
});
|
|
|
|
test("obfuscate_words handles string system field", () => {
|
|
const body = {
|
|
system: "I work with opencode daily.",
|
|
messages: [{ role: "user", content: "hi" }],
|
|
};
|
|
applyTransformPipeline(body, [{ kind: "obfuscate_words", words: ["opencode"] }]);
|
|
assert.ok((body.system as string).includes(`o${ZWJ}pencode`));
|
|
});
|
|
|
|
test("obfuscate_words respects targets — messages only", () => {
|
|
const body = {
|
|
system: [{ type: "text", text: "opencode is here" }],
|
|
messages: [{ role: "user", content: "opencode rocks" }],
|
|
tools: [{ description: "opencode tool" }],
|
|
};
|
|
applyTransformPipeline(body, [
|
|
{ kind: "obfuscate_words", words: ["opencode"], targets: ["messages"] },
|
|
]);
|
|
// System untouched
|
|
assert.equal((body.system[0] as { text: string }).text, "opencode is here");
|
|
// Messages obfuscated
|
|
assert.ok((body.messages[0].content as string).includes(`o${ZWJ}pencode`));
|
|
// Tools untouched
|
|
assert.equal((body.tools[0] as { description: string }).description, "opencode tool");
|
|
});
|
|
|
|
test("obfuscate_words walks tool descriptions (description + function.description)", () => {
|
|
const body = {
|
|
system: [],
|
|
messages: [{ role: "user", content: "hi" }],
|
|
tools: [{ description: "uses opencode" }, { function: { description: "uses open-webui" } }],
|
|
};
|
|
applyTransformPipeline(body, [
|
|
{ kind: "obfuscate_words", words: ["opencode", "open-webui"], targets: ["tools"] },
|
|
]);
|
|
assert.ok((body.tools[0] as { description: string }).description.includes(`o${ZWJ}pencode`));
|
|
assert.ok(
|
|
(body.tools[1] as { function: { description: string } }).function.description.includes(
|
|
`o${ZWJ}pen-webui`
|
|
)
|
|
);
|
|
});
|
|
|
|
test("obfuscate_words is case-insensitive and applies to all targets by default", () => {
|
|
const body = {
|
|
system: [{ type: "text", text: "OpenCode is great" }],
|
|
messages: [{ role: "user", content: "I love OPENCODE" }],
|
|
};
|
|
applyTransformPipeline(body, [{ kind: "obfuscate_words", words: ["opencode"] }]);
|
|
const sys = (body.system[0] as { text: string }).text;
|
|
const msg = body.messages[0].content as string;
|
|
assert.ok(sys.includes(`O${ZWJ}penCode`));
|
|
assert.ok(msg.includes(`O${ZWJ}PENCODE`));
|
|
});
|
|
|
|
test("obfuscate_words with empty list is a no-op", () => {
|
|
const body = {
|
|
system: [{ type: "text", text: "opencode" }],
|
|
messages: [{ role: "user", content: "hi" }],
|
|
};
|
|
applyTransformPipeline(body, [{ kind: "obfuscate_words", words: [] }]);
|
|
assert.equal((body.system[0] as { text: string }).text, "opencode");
|
|
});
|
|
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
// Pipeline ordering: drop paragraph then obfuscate what survives
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
|
|
test("pipeline ordering: drop_paragraph_if_contains runs before obfuscate_words", () => {
|
|
const body = {
|
|
system: [
|
|
{
|
|
type: "text",
|
|
text: "See github.com/open-webui/open-webui\n\nI use openwebui for chat.\n\nAlso try opencode.",
|
|
},
|
|
],
|
|
messages: [{ role: "user", content: "hi" }],
|
|
};
|
|
applyTransformPipeline(body, [
|
|
{
|
|
kind: "drop_paragraph_if_contains",
|
|
needles: ["github.com/open-webui/open-webui"],
|
|
},
|
|
{ kind: "obfuscate_words", words: ["openwebui", "opencode"], targets: ["system"] },
|
|
]);
|
|
const out = (body.system[0] as { text: string }).text;
|
|
assert.ok(!out.includes("github.com/open-webui/open-webui"));
|
|
assert.ok(out.includes(`o${ZWJ}penwebui`));
|
|
assert.ok(out.includes(`o${ZWJ}pencode`));
|
|
});
|
|
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
// Per-provider routing
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
|
|
test("applySystemTransformPipeline: provider not configured → no-op", () => {
|
|
const body = {
|
|
system: [{ type: "text", text: "opencode" }],
|
|
messages: [{ role: "user", content: "hi" }],
|
|
};
|
|
const before = JSON.stringify(body);
|
|
const result = applySystemTransformPipeline("gemini", body, {
|
|
providers: { gemini: { enabled: false, pipeline: [] } },
|
|
});
|
|
assert.equal(result.appliedOpKinds.length, 0);
|
|
assert.equal(JSON.stringify(body), before);
|
|
});
|
|
|
|
test("applySystemTransformPipeline: claude provider runs its default pipeline", () => {
|
|
// Two paragraphs: first contains the OpenWebUI anchor (drop target),
|
|
// second contains a survivable opencode reference (ZWJ target).
|
|
const body = {
|
|
system: [
|
|
{
|
|
type: "text",
|
|
text: "See docs at github.com/open-webui/open-webui\n\nI am opencode helper.",
|
|
},
|
|
],
|
|
messages: [{ role: "user", content: "hi" }],
|
|
};
|
|
// Enable the claude provider explicitly for this test (default is opt-in/disabled).
|
|
const cfg = {
|
|
providers: {
|
|
...DEFAULT_SYSTEM_TRANSFORMS_CONFIG.providers,
|
|
[PROVIDER_CLAUDE]: {
|
|
enabled: true,
|
|
pipeline: DEFAULT_SYSTEM_TRANSFORMS_CONFIG.providers[PROVIDER_CLAUDE].pipeline,
|
|
},
|
|
},
|
|
};
|
|
const result = applySystemTransformPipeline(PROVIDER_CLAUDE, body, cfg);
|
|
const blocks = body.system as Array<{ text: string }>;
|
|
assert.ok(blocks.length >= 1);
|
|
const out = blocks[0].text;
|
|
// Open WebUI anchor paragraph dropped
|
|
assert.ok(!out.includes("github.com/open-webui/open-webui"));
|
|
// ZWJ inserted on opencode
|
|
assert.ok(out.includes(`o${ZWJ}pencode`));
|
|
// No billing header injected (native does that)
|
|
assert.ok(!result.appliedOpKinds.includes("inject_billing_header"));
|
|
});
|
|
|
|
test("applySystemTransformPipeline: claude provider drops Pi documentation paragraph", () => {
|
|
const body = {
|
|
system: [
|
|
{
|
|
type: "text",
|
|
text: [
|
|
"You are an expert coding assistant operating inside pi, a coding agent harness.",
|
|
"Guidelines:\n- Be concise.",
|
|
"Pi documentation (read only when the user asks about pi itself):\n- Main documentation: /Users/test/.nvm/versions/node/v24.11.1/lib/node_modules/@earendil-works/pi-coding-agent/README.md",
|
|
].join("\n\n"),
|
|
},
|
|
],
|
|
messages: [{ role: "user", content: "hi" }],
|
|
};
|
|
const result = applySystemTransformPipeline(PROVIDER_CLAUDE, body);
|
|
const out = (body.system as Array<{ text: string }>)[0].text;
|
|
assert.ok(result.appliedOpKinds.includes("drop_paragraph_if_contains"));
|
|
assert.ok(out.includes("expert coding assistant operating inside pi"));
|
|
assert.ok(out.includes("Guidelines:"));
|
|
assert.ok(!out.includes("@earendil-works/pi-coding-agent"));
|
|
assert.ok(!out.includes("Pi documentation"));
|
|
});
|
|
|
|
test("applySystemTransformPipeline: anthropic-compatible-cc-* falls back to PROVIDER_CC_BRIDGE config", () => {
|
|
const body = {
|
|
system: [{ type: "text", text: "I am OpenCode\n\nThird-party agent" }],
|
|
messages: [{ role: "user", content: "hello world" }],
|
|
};
|
|
const result = applySystemTransformPipeline(
|
|
"anthropic-compatible-cc-claude-opus-4-7",
|
|
body,
|
|
DEFAULT_SYSTEM_TRANSFORMS_CONFIG
|
|
);
|
|
// Full CC bridge pipeline ran → billing header injected at [0]
|
|
assert.ok(result.appliedOpKinds.includes("inject_billing_header"));
|
|
const blocks = body.system as Array<{ text: string }>;
|
|
assert.ok(blocks[0].text.startsWith("x-anthropic-billing-header:"));
|
|
});
|
|
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
// OpenWebUI fixture — the headline bug from issue #2260 comment 4459544580
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
|
|
test("OpenWebUI fixture: claude provider drops anchor + obfuscates 'openwebui' word", () => {
|
|
const body = {
|
|
system: [
|
|
{
|
|
type: "text",
|
|
text: "You are Open WebUI assistant.\n\nDocumentation at github.com/open-webui/open-webui.\n\nThis agent uses openwebui to render messages.",
|
|
},
|
|
],
|
|
messages: [{ role: "user", content: "Tell me about open-webui" }],
|
|
};
|
|
// Enable the claude provider explicitly for this test (default is opt-in/disabled).
|
|
const cfg = {
|
|
providers: {
|
|
...DEFAULT_SYSTEM_TRANSFORMS_CONFIG.providers,
|
|
[PROVIDER_CLAUDE]: {
|
|
enabled: true,
|
|
pipeline: DEFAULT_SYSTEM_TRANSFORMS_CONFIG.providers[PROVIDER_CLAUDE].pipeline,
|
|
},
|
|
},
|
|
};
|
|
applySystemTransformPipeline(PROVIDER_CLAUDE, body, cfg);
|
|
const sysText = (body.system[0] as { text: string }).text;
|
|
// "You are Open WebUI" identity paragraph dropped
|
|
assert.ok(!sysText.includes("You are Open WebUI assistant"));
|
|
// github.com/open-webui/open-webui anchor paragraph dropped
|
|
assert.ok(!sysText.includes("github.com/open-webui/open-webui"));
|
|
// remaining word "openwebui" ZWJ-obfuscated
|
|
assert.ok(sysText.includes(`o${ZWJ}penwebui`));
|
|
// Messages also obfuscated by default targets
|
|
assert.ok((body.messages[0].content as string).includes(`o${ZWJ}pen-webui`));
|
|
});
|
|
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
// Disabled provider → pass-through
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
|
|
test("provider with enabled=false is a pass-through (opt-in posture)", () => {
|
|
const body = {
|
|
system: [{ type: "text", text: "opencode here" }],
|
|
messages: [{ role: "user", content: "hi" }],
|
|
};
|
|
const before = JSON.stringify(body);
|
|
const result = applySystemTransformPipeline(PROVIDER_CLAUDE, body, {
|
|
providers: {
|
|
[PROVIDER_CLAUDE]: { enabled: false, pipeline: DEFAULT_CLAUDE_PIPELINE },
|
|
},
|
|
});
|
|
assert.equal(result.appliedOpKinds.length, 0);
|
|
assert.equal(JSON.stringify(body), before);
|
|
});
|
|
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
// Legacy migration shim
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
|
|
test("setSystemTransformsConfig migrates legacy { enabled, pipeline } into providers[CC_BRIDGE]", () => {
|
|
setSystemTransformsConfig({
|
|
enabled: true,
|
|
pipeline: [
|
|
{
|
|
kind: "replace_text",
|
|
match: "legacy-key-marker",
|
|
replacement: "rewritten",
|
|
allOccurrences: true,
|
|
},
|
|
],
|
|
});
|
|
const cfg = getSystemTransformsConfig();
|
|
const cc = cfg.providers[PROVIDER_CC_BRIDGE];
|
|
assert.ok(cc);
|
|
assert.equal(cc.enabled, true);
|
|
// The custom pipeline is now under the CC bridge provider
|
|
const hasMarker = cc.pipeline.some(
|
|
(op: { kind: string; match?: string }) =>
|
|
op.kind === "replace_text" && op.match === "legacy-key-marker"
|
|
);
|
|
assert.ok(hasMarker);
|
|
// Other providers still come from defaults (claude pipeline preserved)
|
|
assert.ok(cfg.providers[PROVIDER_CLAUDE]);
|
|
resetSystemTransformsConfig();
|
|
});
|
|
|
|
test("setSystemTransformsConfig accepts per-provider shape and merges defaults for unset providers", () => {
|
|
setSystemTransformsConfig({
|
|
providers: {
|
|
gemini: {
|
|
enabled: true,
|
|
pipeline: [{ kind: "obfuscate_words", words: ["gemini-cli"] }],
|
|
},
|
|
},
|
|
});
|
|
const cfg = getSystemTransformsConfig();
|
|
assert.ok(cfg.providers.gemini);
|
|
assert.equal(cfg.providers.gemini.enabled, true);
|
|
// Defaults still present for unset providers (no regression for claude/cc).
|
|
assert.ok(cfg.providers[PROVIDER_CLAUDE]);
|
|
assert.ok(cfg.providers[PROVIDER_CC_BRIDGE]);
|
|
resetSystemTransformsConfig();
|
|
});
|
|
|
|
test("setSystemTransformsConfig(null) resets to defaults", () => {
|
|
setSystemTransformsConfig({
|
|
providers: { custom: { enabled: true, pipeline: [] } },
|
|
});
|
|
setSystemTransformsConfig(null);
|
|
const cfg = getSystemTransformsConfig();
|
|
// Defaults restored — `custom` key dropped.
|
|
assert.ok(!cfg.providers.custom);
|
|
assert.ok(cfg.providers[PROVIDER_CLAUDE]);
|
|
assert.ok(cfg.providers[PROVIDER_CC_BRIDGE]);
|
|
resetSystemTransformsConfig();
|
|
});
|
|
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
// Idempotency
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
|
|
test("idempotency: obfuscate_words running twice does not double-ZWJ", () => {
|
|
const body = {
|
|
system: [{ type: "text", text: "opencode here" }],
|
|
messages: [{ role: "user", content: "hi" }],
|
|
};
|
|
applyTransformPipeline(body, [
|
|
{ kind: "obfuscate_words", words: ["opencode"], targets: ["system"] },
|
|
]);
|
|
const once = (body.system[0] as { text: string }).text;
|
|
applyTransformPipeline(body, [
|
|
{ kind: "obfuscate_words", words: ["opencode"], targets: ["system"] },
|
|
]);
|
|
const twice = (body.system[0] as { text: string }).text;
|
|
// Second pass cannot find "opencode" — the ZWJ broke it — so no further change.
|
|
assert.equal(once, twice);
|
|
});
|
|
|
|
test("idempotency: full claude pipeline running twice does not duplicate blocks", () => {
|
|
const body = {
|
|
system: [
|
|
{
|
|
type: "text",
|
|
text: "You are Open WebUI helper.\n\nopenwebui is the platform.",
|
|
},
|
|
],
|
|
messages: [{ role: "user", content: "hi" }],
|
|
};
|
|
applySystemTransformPipeline(PROVIDER_CLAUDE, body, DEFAULT_SYSTEM_TRANSFORMS_CONFIG);
|
|
const onceLen = (body.system as Array<unknown>).length;
|
|
applySystemTransformPipeline(PROVIDER_CLAUDE, body, DEFAULT_SYSTEM_TRANSFORMS_CONFIG);
|
|
const twiceLen = (body.system as Array<unknown>).length;
|
|
assert.equal(onceLen, twiceLen);
|
|
});
|
|
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
// UI ↔ server defaults parity
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
//
|
|
// The Settings UI keeps a hand-maintained mirror of DEFAULT_SYSTEM_TRANSFORMS_CONFIG
|
|
// in src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx so it can
|
|
// render + reset to defaults without a server roundtrip. The snapshot below is
|
|
// the contract between server and UI — if it drifts, both must be updated in
|
|
// the same commit.
|
|
|
|
const UI_DEFAULTS_SNAPSHOT = {
|
|
providers: {
|
|
claude: {
|
|
enabled: true,
|
|
pipeline: [
|
|
{
|
|
kind: "drop_paragraph_if_contains",
|
|
needles: [
|
|
"github.com/anomalyco/opencode",
|
|
"opencode.ai/docs",
|
|
"github.com/cline/cline",
|
|
"github.com/getcursor/cursor",
|
|
"continue.dev",
|
|
"github.com/open-webui/open-webui",
|
|
"openwebui.com",
|
|
"docs.openwebui.com",
|
|
"@earendil-works/pi-coding-agent",
|
|
"/.pi/",
|
|
"Pi documentation (read only when the user asks about pi itself",
|
|
],
|
|
},
|
|
{
|
|
kind: "drop_paragraph_if_starts_with",
|
|
prefixes: ["You are OpenCode", "You are Open WebUI"],
|
|
},
|
|
{
|
|
kind: "replace_text",
|
|
match: "if OpenCode honestly",
|
|
replacement: "if the assistant honestly",
|
|
allOccurrences: true,
|
|
},
|
|
{
|
|
kind: "replace_text",
|
|
match: "Here is some useful information about the environment you are running in:",
|
|
replacement: "Environment context you are running in:",
|
|
allOccurrences: true,
|
|
},
|
|
{
|
|
kind: "obfuscate_words",
|
|
words: [
|
|
"opencode",
|
|
"open-code",
|
|
"cline",
|
|
"roo-cline",
|
|
"roo_cline",
|
|
"cursor",
|
|
"windsurf",
|
|
"aider",
|
|
"continue.dev",
|
|
"copilot",
|
|
"avante",
|
|
"codecompanion",
|
|
"openwebui",
|
|
"open-webui",
|
|
],
|
|
targets: ["system", "messages", "tools"],
|
|
},
|
|
],
|
|
},
|
|
"anthropic-compatible-cc": {
|
|
enabled: true,
|
|
pipeline: [
|
|
{
|
|
kind: "drop_paragraph_if_contains",
|
|
needles: ["github.com/open-webui/open-webui", "openwebui.com", "docs.openwebui.com"],
|
|
},
|
|
{
|
|
kind: "drop_paragraph_if_starts_with",
|
|
prefixes: ["You are Open WebUI"],
|
|
},
|
|
{
|
|
kind: "obfuscate_words",
|
|
words: ["openwebui", "open-webui"],
|
|
targets: ["system", "messages", "tools"],
|
|
},
|
|
{
|
|
kind: "drop_paragraph_if_contains",
|
|
needles: [
|
|
"github.com/anomalyco/opencode",
|
|
"opencode.ai/docs",
|
|
"github.com/cline/cline",
|
|
"github.com/getcursor/cursor",
|
|
"continue.dev",
|
|
],
|
|
},
|
|
{
|
|
kind: "drop_paragraph_if_starts_with",
|
|
prefixes: ["You are OpenCode"],
|
|
},
|
|
{
|
|
kind: "replace_text",
|
|
match: "if OpenCode honestly",
|
|
replacement: "if the assistant honestly",
|
|
allOccurrences: true,
|
|
},
|
|
{
|
|
kind: "replace_text",
|
|
match: "Here is some useful information about the environment you are running in:",
|
|
replacement: "Environment context you are running in:",
|
|
allOccurrences: true,
|
|
},
|
|
{
|
|
kind: "prepend_system_block",
|
|
text: "You are a Claude agent, built on Anthropic's Claude Agent SDK.",
|
|
idempotencyKey: "claude-agent-sdk-identity",
|
|
},
|
|
{
|
|
kind: "inject_billing_header",
|
|
entrypoint: "sdk-cli",
|
|
versionFormat: "ex-machina",
|
|
cchAlgo: "sha256-first-user",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
};
|
|
|
|
test("defaults parity: DEFAULT_SYSTEM_TRANSFORMS_CONFIG matches the UI mirror snapshot", () => {
|
|
assert.deepEqual(
|
|
JSON.parse(JSON.stringify(DEFAULT_SYSTEM_TRANSFORMS_CONFIG)),
|
|
UI_DEFAULTS_SNAPSHOT,
|
|
"Server DEFAULT_SYSTEM_TRANSFORMS_CONFIG drifted from the UI mirror in " +
|
|
"src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx " +
|
|
"(DEFAULT_SYSTEM_TRANSFORMS_CLIENT). Update both in the same commit."
|
|
);
|
|
});
|