"use client"; import { useEffect, useMemo, useState } from "react"; import { Button, Card, Collapsible, Input, Select, Toggle } from "@/shared/components"; import { useTranslations } from "next-intl"; import { useNotificationStore } from "@/store/notificationStore"; import { CLI_COMPAT_PROVIDER_DISPLAY, CLI_COMPAT_TOGGLE_IDS, normalizeCliCompatProviderId, } from "@/shared/constants/cliCompatProviders"; import { AI_PROVIDERS } from "@/shared/constants/providers"; import { compareTr } from "@/shared/utils/turkishText"; // Provider keys (mirror of open-sse/services/systemTransforms.ts). const PROVIDER_CLAUDE = "claude"; const PROVIDER_CC_BRIDGE = "anthropic-compatible-cc"; const BUILTIN_PROVIDERS = new Set([PROVIDER_CLAUDE, PROVIDER_CC_BRIDGE]); // Canonical provider catalog for the "Add provider" dropdown. Pulled from the // shared AI_PROVIDERS registry so the UI stays in sync with backend provider // definitions. We add the CC bridge synthetic ID (no AI_PROVIDERS entry — it's // a relay surface, not an upstream provider). Sorted by display name. type ProviderCatalogEntry = { id: string; name: string }; const PROVIDER_CATALOG: ProviderCatalogEntry[] = (() => { const entries: ProviderCatalogEntry[] = Object.values(AI_PROVIDERS).map((p) => ({ id: p.id, name: p.name ?? p.id, })); entries.push({ id: PROVIDER_CC_BRIDGE, name: "Anthropic-compatible CC bridge" }); entries.sort((a, b) => compareTr(a.name, b.name)); return entries; })(); const OPENWEBUI_PARAGRAPH_ANCHORS = [ "github.com/open-webui/open-webui", "openwebui.com", "docs.openwebui.com", ]; // Mirrors of ccBridgeTransforms.ts constants used by the native `claude` and // CC-bridge default pipelines. const DEFAULT_PARAGRAPH_REMOVAL_ANCHORS = [ "github.com/anomalyco/opencode", "opencode.ai/docs", "github.com/cline/cline", "github.com/getcursor/cursor", "continue.dev", ]; const PI_PARAGRAPH_ANCHORS = [ "@earendil-works/pi-coding-agent", "/.pi/", "Pi documentation (read only when the user asks about pi itself", ]; const DEFAULT_IDENTITY_PREFIXES = ["You are OpenCode"]; const DEFAULT_TEXT_REPLACEMENTS = [ { match: "if OpenCode honestly", replacement: "if the assistant honestly" }, { match: "Here is some useful information about the environment you are running in:", replacement: "Environment context you are running in:", }, ]; const DEFAULT_OBFUSCATE_WORDS = [ "opencode", "open-code", "cline", "roo-cline", "roo_cline", "cursor", "windsurf", "aider", "continue.dev", "copilot", "avante", "codecompanion", "openwebui", "open-webui", ]; // Mirror of DEFAULT_SYSTEM_TRANSFORMS_CONFIG from open-sse/services/systemTransforms.ts. // Kept client-side so the UI can render + reset to defaults without a server roundtrip. // Server remains the source of truth — UI just lets the user inspect, edit, and reset. const DEFAULT_SYSTEM_TRANSFORMS_CLIENT = { providers: { [PROVIDER_CLAUDE]: { enabled: true, pipeline: [ { kind: "drop_paragraph_if_contains", needles: [ ...DEFAULT_PARAGRAPH_REMOVAL_ANCHORS, ...OPENWEBUI_PARAGRAPH_ANCHORS, ...PI_PARAGRAPH_ANCHORS, ], }, { kind: "drop_paragraph_if_starts_with", prefixes: [...DEFAULT_IDENTITY_PREFIXES, "You are Open WebUI"], }, ...DEFAULT_TEXT_REPLACEMENTS.map((r) => ({ kind: "replace_text" as const, match: r.match, replacement: r.replacement, allOccurrences: true, })), { kind: "obfuscate_words", words: [...DEFAULT_OBFUSCATE_WORDS], targets: ["system", "messages", "tools"], }, ], }, [PROVIDER_CC_BRIDGE]: { enabled: true, pipeline: [ { kind: "drop_paragraph_if_contains", needles: [...OPENWEBUI_PARAGRAPH_ANCHORS], }, { 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", }, ], }, }, } as const; const PROVIDER_TILE_DISPLAY: Record< string, { name: string; description: string; icon: string; tone: string } > = { [PROVIDER_CLAUDE]: { name: "Claude (OAuth)", description: "Native Claude provider with OAuth-issued tokens.", icon: "anthropic", tone: "indigo", }, [PROVIDER_CC_BRIDGE]: { name: "Claude-Code Bridge", description: "Relay endpoints using API keys (anthropic-compatible-cc-*).", icon: "hub", tone: "purple", }, }; type TransformOpKind = | "drop_paragraph_if_contains" | "drop_paragraph_if_starts_with" | "replace_text" | "replace_regex" | "drop_block_if_contains" | "prepend_system_block" | "append_system_block" | "inject_billing_header" | "obfuscate_words"; const OP_KIND_LABELS: Record = { drop_paragraph_if_contains: "routingOpDropParagraphContainsLabel", drop_paragraph_if_starts_with: "routingOpDropParagraphStartsWithLabel", replace_text: "routingOpReplaceTextLabel", replace_regex: "routingOpReplaceRegexLabel", drop_block_if_contains: "routingOpDropBlockContainsLabel", prepend_system_block: "routingOpPrependSystemBlockLabel", append_system_block: "routingOpAppendSystemBlockLabel", inject_billing_header: "routingOpInjectBillingHeaderLabel", obfuscate_words: "routingOpObfuscateWordsLabel", }; // Human-readable description shown above each op's editor. Explains in one // sentence what the op DOES (transformation effect) and one sentence WHEN // to use it (the typical fingerprint-sanitization use-case). const OP_KIND_DESCRIPTIONS: Record = { drop_paragraph_if_contains: "routingOpDropParagraphContainsDesc", drop_paragraph_if_starts_with: "routingOpDropParagraphStartsWithDesc", replace_text: "routingOpReplaceTextDesc", replace_regex: "routingOpReplaceRegexDesc", drop_block_if_contains: "routingOpDropBlockContainsDesc", prepend_system_block: "routingOpPrependSystemBlockDesc", append_system_block: "routingOpAppendSystemBlockDesc", inject_billing_header: "routingOpInjectBillingHeaderDesc", obfuscate_words: "routingOpObfuscateWordsDesc", }; // Per-field hints rendered under each Input/Select/Toggle inside the // editor. Short, plain-English. Keep under ~120 chars each. const FIELD_HINTS = { needles: "routingNeedlesHint", prefixes: "routingPrefixesHint", caseSensitive: "routingCaseSensitiveHint", matchLiteral: "routingMatchLiteralHint", replacementText: "routingReplacementTextHint", allOccurrences: "routingAllOccurrencesHint", pattern: "routingPatternHint", regexFlags: "routingRegexFlagsHint", blockText: "routingBlockTextHint", idempotencyKey: "routingIdempotencyKeyHint", billingEntrypoint: "routingBillingEntrypointHint", billingVersionFormat: "routingBillingVersionFormatHint", billingCchAlgo: "routingBillingCchAlgoHint", obfuscateWords: "routingObfuscateWordsHint", obfuscateTargets: "routingObfuscateTargetsHint", }; function makeDefaultOp(kind: TransformOpKind): any { switch (kind) { case "drop_paragraph_if_contains": return { kind, needles: [""] }; case "drop_paragraph_if_starts_with": return { kind, prefixes: [""] }; case "replace_text": return { kind, match: "", replacement: "", allOccurrences: true }; case "replace_regex": return { kind, pattern: "", flags: "g", replacement: "" }; case "drop_block_if_contains": return { kind, needles: [""] }; case "prepend_system_block": return { kind, text: "", idempotencyKey: "" }; case "append_system_block": return { kind, text: "", idempotencyKey: "" }; case "inject_billing_header": return { kind, entrypoint: "sdk-cli", versionFormat: "ex-machina", cchAlgo: "sha256-first-user", }; case "obfuscate_words": return { kind, words: [""], targets: ["system", "messages", "tools"] }; } } function StringListEditor({ label, hint, items, onChange, disabled, }: { label: string; hint?: string; items: string[]; onChange: (next: string[]) => void; disabled?: boolean; }) { const t = useTranslations("settings"); const tCommon = useTranslations("common"); return (
{label} {hint &&

{hint}

} {items.map((item, idx) => (
{ const next = [...items]; next[idx] = e.target.value; onChange(next); }} />
))}
); } function OpEditor({ op, onChange, disabled, }: { op: any; onChange: (next: any) => void; disabled?: boolean; }) { const t = useTranslations("settings"); const updateField = (field: string, value: any) => onChange({ ...op, [field]: value }); const kind = op?.kind as TransformOpKind | undefined; const opDescription = kind ? t(OP_KIND_DESCRIPTIONS[kind]) : null; const wrap = (body: React.ReactNode) => (
{opDescription && (

{opDescription}

)} {body}
); switch (op?.kind) { case "drop_paragraph_if_contains": return wrap(
updateField("needles", next)} disabled={disabled} /> updateField("caseSensitive", c)} size="sm" disabled={disabled} />
); case "drop_paragraph_if_starts_with": return wrap(
updateField("prefixes", next)} disabled={disabled} /> updateField("caseSensitive", c)} size="sm" disabled={disabled} />
); case "replace_text": return wrap(
updateField("match", e.target.value)} /> updateField("replacement", e.target.value)} /> updateField("allOccurrences", c)} size="sm" disabled={disabled} />
); case "replace_regex": return wrap(
updateField("pattern", e.target.value)} /> updateField("flags", e.target.value)} /> updateField("replacement", e.target.value)} />
); case "drop_block_if_contains": return wrap( updateField("needles", next)} disabled={disabled} /> ); case "prepend_system_block": case "append_system_block": return wrap(