Files
CC-Switch/src/components/settings/RectifierConfigPanel.tsx
T
Keith Yu 8217bfff50 feat: add Bedrock request optimizer (PRE-SEND thinking + cache injection) (#1301)
* feat: add Bedrock request optimizer (PRE-SEND thinking + cache injection)

Add a PRE-SEND request optimizer that enhances Bedrock API requests
before forwarding, complementing the existing POST-ERROR rectifier system.

New modules:
- thinking_optimizer: 3-path model detection (adaptive/legacy/skip)
  - Opus 4.6/Sonnet 4.6: adaptive thinking + effort max + 1M context beta
  - Legacy models: inject extended thinking with max budget
  - Haiku: skip (no modification)
- cache_injector: auto-inject cache_control breakpoints (max 4)
  - Injects at tools/system/assistant message positions
  - TTL upgrade for existing breakpoints (5m → 1h)

Gate: only activates for Bedrock providers (CLAUDE_CODE_USE_BEDROCK=1)
Config: stored in SQLite settings table, default OFF, user opt-in
UI: new Optimizer section in RectifierConfigPanel with 3 toggles + TTL

18 unit tests covering all paths. Verified against live Bedrock API.

* chore: remove docs/plans directory

* fix: address code review findings for Bedrock request optimizer

P0 fixes:
- Replace hardcoded Chinese with i18n t() calls in optimizer panel,
  add translation keys to zh/en/ja locale files
- Fix u64 underflow: max_tokens - 1 → max_tokens.saturating_sub(1)
- Move optimizer from before retry loop to per-provider with body
  cloning, preventing Bedrock fields leaking to non-Bedrock providers

P1 fixes:
- Replace .map() side-effect pattern with idiomatic if-let (clippy)
- Fix module alphabetical ordering in mod.rs
- Add cache_ttl whitelist validation in set_optimizer_config
- Remove #[allow(unused_assignments)] and dead budget decrement

---------

Co-authored-by: Keith (via OpenClaw) <keithyt06@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
2026-03-07 18:57:21 +08:00

206 lines
7.0 KiB
TypeScript

import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import {
settingsApi,
type RectifierConfig,
type OptimizerConfig,
} from "@/lib/api/settings";
export function RectifierConfigPanel() {
const { t } = useTranslation();
const [config, setConfig] = useState<RectifierConfig>({
enabled: true,
requestThinkingSignature: true,
requestThinkingBudget: true,
});
const [optimizerConfig, setOptimizerConfig] = useState<OptimizerConfig>({
enabled: false,
thinkingOptimizer: true,
cacheInjection: true,
cacheTtl: "1h",
});
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
settingsApi
.getRectifierConfig()
.then(setConfig)
.catch((e) => console.error("Failed to load rectifier config:", e))
.finally(() => setIsLoading(false));
settingsApi
.getOptimizerConfig()
.then(setOptimizerConfig)
.catch((e) => console.error("Failed to load optimizer config:", e));
}, []);
const handleChange = async (updates: Partial<RectifierConfig>) => {
const newConfig = { ...config, ...updates };
setConfig(newConfig);
try {
await settingsApi.setRectifierConfig(newConfig);
} catch (e) {
console.error("Failed to save rectifier config:", e);
toast.error(String(e));
setConfig(config);
}
};
const handleOptimizerChange = async (updates: Partial<OptimizerConfig>) => {
const newConfig = { ...optimizerConfig, ...updates };
setOptimizerConfig(newConfig);
try {
await settingsApi.setOptimizerConfig(newConfig);
} catch (e) {
console.error("Failed to save optimizer config:", e);
toast.error(String(e));
setOptimizerConfig(optimizerConfig);
}
};
if (isLoading) return null;
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>{t("settings.advanced.rectifier.enabled")}</Label>
<p className="text-xs text-muted-foreground">
{t("settings.advanced.rectifier.enabledDescription")}
</p>
</div>
<Switch
checked={config.enabled}
onCheckedChange={(checked) => handleChange({ enabled: checked })}
/>
</div>
<div className="space-y-4">
<h4 className="text-sm font-medium text-muted-foreground">
{t("settings.advanced.rectifier.requestGroup")}
</h4>
<div className="flex items-center justify-between pl-4">
<div className="space-y-0.5">
<Label>{t("settings.advanced.rectifier.thinkingSignature")}</Label>
<p className="text-xs text-muted-foreground">
{t("settings.advanced.rectifier.thinkingSignatureDescription")}
</p>
</div>
<Switch
checked={config.requestThinkingSignature}
disabled={!config.enabled}
onCheckedChange={(checked) =>
handleChange({ requestThinkingSignature: checked })
}
/>
</div>
<div className="flex items-center justify-between pl-4">
<div className="space-y-0.5">
<Label>{t("settings.advanced.rectifier.thinkingBudget")}</Label>
<p className="text-xs text-muted-foreground">
{t("settings.advanced.rectifier.thinkingBudgetDescription")}
</p>
</div>
<Switch
checked={config.requestThinkingBudget}
disabled={!config.enabled}
onCheckedChange={(checked) =>
handleChange({ requestThinkingBudget: checked })
}
/>
</div>
</div>
<div className="border-t pt-6 mt-6">
<div className="space-y-1 mb-4">
<h3 className="text-sm font-medium">
{t("settings.advanced.optimizer.title")}
</h3>
<p className="text-xs text-muted-foreground">
{t("settings.advanced.optimizer.description")}
</p>
</div>
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>{t("settings.advanced.optimizer.enabled")}</Label>
</div>
<Switch
checked={optimizerConfig.enabled}
onCheckedChange={(checked) =>
handleOptimizerChange({ enabled: checked })
}
/>
</div>
<div className="space-y-4 pl-4">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>
{t("settings.advanced.optimizer.thinkingOptimizer")}
</Label>
<p className="text-xs text-muted-foreground">
{t(
"settings.advanced.optimizer.thinkingOptimizerDescription",
)}
</p>
</div>
<Switch
checked={optimizerConfig.thinkingOptimizer}
disabled={!optimizerConfig.enabled}
onCheckedChange={(checked) =>
handleOptimizerChange({ thinkingOptimizer: checked })
}
/>
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>{t("settings.advanced.optimizer.cacheInjection")}</Label>
<p className="text-xs text-muted-foreground">
{t("settings.advanced.optimizer.cacheInjectionDescription")}
</p>
</div>
<Switch
checked={optimizerConfig.cacheInjection}
disabled={!optimizerConfig.enabled}
onCheckedChange={(checked) =>
handleOptimizerChange({ cacheInjection: checked })
}
/>
</div>
{optimizerConfig.cacheInjection && (
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>{t("settings.advanced.optimizer.cacheTtl")}</Label>
</div>
<select
className="h-9 rounded-md border border-input bg-background px-3 text-sm"
value={optimizerConfig.cacheTtl}
disabled={
!optimizerConfig.enabled || !optimizerConfig.cacheInjection
}
onChange={(e) =>
handleOptimizerChange({ cacheTtl: e.target.value })
}
>
<option value="5m">
{t("settings.advanced.optimizer.cacheTtl5m")}
</option>
<option value="1h">
{t("settings.advanced.optimizer.cacheTtl1h")}
</option>
</select>
</div>
)}
</div>
</div>
</div>
</div>
);
}