feat: add wildcard path support and improve param override templates/editor

This commit is contained in:
Seefs
2026-03-05 16:39:34 +08:00
parent 887a929d65
commit 16e4ce52e3
3 changed files with 512 additions and 24 deletions

View File

@@ -276,6 +276,7 @@ const LEGACY_TEMPLATE = {
const OPERATION_TEMPLATE = {
operations: [
{
description: 'Set default temperature for openai/* models.',
path: 'temperature',
mode: 'set',
value: 0.7,
@@ -294,8 +295,9 @@ const OPERATION_TEMPLATE = {
const HEADER_PASSTHROUGH_TEMPLATE = {
operations: [
{
description: 'Pass through X-Request-Id header to upstream.',
mode: 'pass_headers',
value: ['Authorization'],
value: ['X-Request-Id'],
keep_origin: true,
},
],
@@ -304,6 +306,8 @@ const HEADER_PASSTHROUGH_TEMPLATE = {
const GEMINI_IMAGE_4K_TEMPLATE = {
operations: [
{
description:
'Set imageSize to 4K when model contains gemini/image and ends with 4k.',
mode: 'set',
path: 'generationConfig.imageConfig.imageSize',
value: '4K',
@@ -311,7 +315,17 @@ const GEMINI_IMAGE_4K_TEMPLATE = {
{
path: 'original_model',
mode: 'contains',
value: 'gemini-3-pro-image-preview',
value: 'gemini',
},
{
path: 'original_model',
mode: 'contains',
value: 'image',
},
{
path: 'original_model',
mode: 'suffix',
value: '4k',
},
],
logic: 'AND',
@@ -319,11 +333,13 @@ const GEMINI_IMAGE_4K_TEMPLATE = {
],
};
const AWS_BEDROCK_ANTHROPIC_BETA_OVERRIDE_TEMPLATE = {
const AWS_BEDROCK_ANTHROPIC_COMPAT_TEMPLATE = {
operations: [
{
description: 'Normalize anthropic-beta header tokens for Bedrock compatibility.',
mode: 'set_header',
path: 'anthropic-beta',
// https://github.com/BerriAI/litellm/blob/main/litellm/anthropic_beta_headers_config.json
value: {
'advanced-tool-use-2025-11-20': 'tool-search-tool-2025-10-19',
bash_20241022: null,
@@ -355,6 +371,11 @@ const AWS_BEDROCK_ANTHROPIC_BETA_OVERRIDE_TEMPLATE = {
'web-search-2025-03-05': null,
},
},
{
description: 'Remove all tools[*].custom.input_examples before upstream relay.',
mode: 'delete',
path: 'tools.*.custom.input_examples',
},
],
};
@@ -378,7 +399,7 @@ const TEMPLATE_PRESET_CONFIG = {
},
pass_headers_auth: {
group: 'scenario',
label: '请求头透传(Authorization',
label: '请求头透传(X-Request-Id',
kind: 'operations',
payload: HEADER_PASSTHROUGH_TEMPLATE,
},
@@ -402,9 +423,9 @@ const TEMPLATE_PRESET_CONFIG = {
},
aws_bedrock_anthropic_beta_override: {
group: 'scenario',
label: 'AWS Bedrock anthropic-beta覆盖',
label: 'AWS Bedrock Claude 兼容模板',
kind: 'operations',
payload: AWS_BEDROCK_ANTHROPIC_BETA_OVERRIDE_TEMPLATE,
payload: AWS_BEDROCK_ANTHROPIC_COMPAT_TEMPLATE,
},
};
@@ -764,6 +785,7 @@ const createDefaultCondition = () => normalizeCondition({});
const normalizeOperation = (operation = {}) => ({
id: nextLocalId(),
description: typeof operation.description === 'string' ? operation.description : '',
path: typeof operation.path === 'string' ? operation.path : '',
mode: OPERATION_MODE_VALUES.has(operation.mode) ? operation.mode : 'set',
value_text: toValueText(operation.value),
@@ -1086,6 +1108,7 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
if (!keyword) return operations;
return operations.filter((operation) => {
const searchableText = [
operation.description,
operation.mode,
operation.path,
operation.from,
@@ -1151,10 +1174,14 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
const payloadOps = filteredOps.map((operation) => {
const mode = operation.mode || 'set';
const meta = MODE_META[mode] || MODE_META.set;
const descriptionValue = String(operation.description || '').trim();
const pathValue = operation.path.trim();
const fromValue = operation.from.trim();
const toValue = operation.to.trim();
const payload = { mode };
if (descriptionValue) {
payload.description = descriptionValue;
}
if (meta.path) {
payload.path = pathValue;
}
@@ -1563,6 +1590,7 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
if (index < 0) return prev;
const source = prev[index];
const cloned = normalizeOperation({
description: source.description,
path: source.path,
mode: source.mode,
value: parseLooseValue(source.value_text),
@@ -1891,7 +1919,7 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
<Input
value={operationSearch}
placeholder={t('搜索规则(类型 / 路径 / 来源 / 目标)')}
placeholder={t('搜索规则(描述 / 类型 / 路径 / 来源 / 目标)')}
onChange={(nextValue) =>
setOperationSearch(nextValue || '')
}
@@ -1958,6 +1986,23 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
>
{getOperationSummary(operation, index)}
</Text>
{String(operation.description || '').trim() ? (
<Text
type='tertiary'
size='small'
className='block mt-1'
style={{
lineHeight: 1.5,
wordBreak: 'break-word',
overflow: 'hidden',
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
}}
>
{operation.description}
</Text>
) : null}
</div>
<Tag size='small' color='grey'>
{(operation.conditions || []).length}
@@ -2035,6 +2080,7 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
type='danger'
theme='borderless'
icon={<IconDelete />}
aria-label={t('删除规则')}
onClick={() =>
removeOperation(selectedOperation.id)
}
@@ -2085,6 +2131,25 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
>
{MODE_DESCRIPTIONS[mode] || ''}
</Text>
<div className='mt-2'>
<Text type='tertiary' size='small'>
{t('规则描述(可选)')}
</Text>
<Input
value={selectedOperation.description || ''}
placeholder={t('例如:清理工具参数,避免上游校验错误')}
onChange={(nextValue) =>
updateOperation(selectedOperation.id, {
description: nextValue || '',
})
}
maxLength={180}
showClear
/>
<Text type='tertiary' size='small' className='mt-1 block'>
{`${String(selectedOperation.description || '').length}/180`}
</Text>
</div>
{meta.value ? (
mode === 'return_error' && returnErrorDraft ? (