mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-20 06:02:14 +03:00
v2 of the CC bridge body transforms (issue #2260). Generalizes the
single-provider `ccBridgeTransforms` config (commit e3e962db) into a
per-provider registry keyed by OmniRoute provider id, and wires the
native `claude` OAuth path into the same DSL so raw `claude/<model>`
requests no longer bypass the sanitization layer.
Reference: comment 4459544580 reported a 429 on raw `claude/<model>`
from Open WebUI; CC-bridge-only v1 didn't help that path. v2 closes
three gaps named in the comment:
1. `openwebui` / `open-webui` added to the default obfuscation
word list (new `obfuscate_words` op kind, configurable).
2. Native `claude` provider path now runs the per-provider pipeline
after its existing billing+sentinel prepend (executors/base.ts).
Its default pipeline is cosmetic only (Open WebUI paragraph
anchors + identity-prefix drop + ZWJ obfuscation); it deliberately
omits `inject_billing_header` so it never collides with the native
prepend.
3. Open WebUI paragraph anchors (github.com/open-webui/open-webui,
openwebui.com, docs.openwebui.com) and identity prefix
("You are Open WebUI") added to both `claude` and CC bridge
default pipelines.
API surface:
- New module: open-sse/services/systemTransforms.ts
* `SystemTransformsConfig { providers: Record<id, { enabled, pipeline }> }`
* `TransformOp` extends the base CC bridge op set with
`obfuscate_words { words[], targets[] }`.
* `applySystemTransformPipeline(providerId, body, config?)`
routes to the right per-provider pipeline (with CC bridge prefix
match so `anthropic-compatible-cc-*` all share one config).
* `setSystemTransformsConfig` accepts both legacy single-provider
shape (migrates into providers[anthropic-compatible-cc]) and the
new per-provider shape (merges with defaults for unset providers).
- Native claude wedge: executors/base.ts now calls
`applySystemTransformPipeline(PROVIDER_CLAUDE, tb)` after the
existing billing+sentinel prepend (line ~789).
- CC bridge step 5b: claudeCodeCompatible.ts step 5b switched from
`applyCcBridgeTransformPipeline` (single-config) to
`applySystemTransformPipeline(PROVIDER_CC_BRIDGE, body)`. The
underlying ccBridgeTransforms.ts module remains the base executor
that systemTransforms.ts delegates to for the shared op kinds —
no rename to keep the diff reviewable, and all 30 base-op tests
stay green.
- Settings:
* Zod schema gains `systemTransforms.providers[*]` with full
discriminated union over the 9 op kinds (including
obfuscate_words).
* Legacy `ccBridgeTransforms` zod field kept for back-compat read;
runtime now feeds it through `setSystemTransformsConfig` migration
shim so persisted Phase-2 data keeps working. v2 `systemTransforms`
wins on conflict (applied last).
* Runtime settings registry gains `systemTransforms` reload section
next to legacy `ccBridgeTransforms`.
- UI rewrite (RoutingTab.tsx): the two standalone cards (CLI
Fingerprint + CC Bridge Transforms) collapse into one
"Provider Upstream Compatibility" card. Per-provider tiles render
the pipeline summary + JSON editor with client-side shape
validation + Apply / Reset. Copy clarifies that no local Claude
Code binary is required (the lock badge on the Claude tile means
the fingerprint is force-applied for OAuth account safety, not
that the user must install the CLI).
Tests:
- tests/unit/system-transforms.test.ts (22 new tests covering
defaults, per-op semantics, ordering, per-provider routing, opt-in
pass-through, Open WebUI fixture, migration shim, idempotency).
- tests/unit/cc-bridge-transforms.test.ts (30 base-op tests
unchanged, still green).
- tests/unit/claude-code-compatible-{helpers,request}.test.ts and
8 related claude/cc/executor test files all green (140 tests).
Closes #2260.
375 lines
17 KiB
TypeScript
375 lines
17 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,
|
|
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"));
|
|
});
|
|
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
// 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" }],
|
|
};
|
|
const result = applySystemTransformPipeline(
|
|
PROVIDER_CLAUDE,
|
|
body,
|
|
DEFAULT_SYSTEM_TRANSFORMS_CONFIG
|
|
);
|
|
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: 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" }],
|
|
};
|
|
applySystemTransformPipeline(PROVIDER_CLAUDE, body, DEFAULT_SYSTEM_TRANSFORMS_CONFIG);
|
|
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);
|
|
});
|