fix(provider): 修正媒体请求转换与上游错误透传

按上游协议能力延迟处理媒体资源:OpenAI 兼容平台默认使用 multipart,显式配置后才发送 JSON URL;Gemini 官方协议使用 Files API,兼容协议使用内嵌 Base64,并同步覆盖相关媒体客户端。\n\n安全的上游 400/422 原始错误会作为下游 message 返回,同时保留结构化诊断信息和历史任务兼容。\n\n验证:API 全量无缓存测试、go vet、pnpm lint、pnpm test、pnpm build、pnpm openapi、git diff --check。
This commit is contained in:
2026-08-05 00:40:42 +08:00
parent c79c2a7b44
commit ebdb96e7d7
19 changed files with 1163 additions and 93 deletions
@@ -504,6 +504,16 @@ export function PlatformManagementPanel(props: {
<ToggleField checked={form.supportBase64Input} label="支持 Base64 输入" onChange={(checked) => setForm({ ...form, supportBase64Input: checked })} />
<ToggleField checked={form.supportUrlInput} label="支持 URL 输入" onChange={(checked) => setForm({ ...form, supportUrlInput: checked })} />
</div>
<Label>
OpenAI
<Select
value={form.imageEditRequestFormat}
onChange={(event) => setForm({ ...form, imageEditRequestFormat: event.target.value as PlatformWizardForm['imageEditRequestFormat'] })}
>
<option value="multipart">Multipart </option>
<option value="json_url">JSON URL</option>
</Select>
</Label>
</FormSection>
<FormSection icon={<Boxes size={16} />} title={`模型绑定 · ${selectedModels.length}/${availableModels.length}`}>
@@ -1192,6 +1202,7 @@ function platformToForm(
httpProxy: networkProxy.httpProxy,
supportBase64Input: readBoolean(config, 'supportBase64Input', true),
supportUrlInput: readBoolean(config, 'supportUrlInput', true),
imageEditRequestFormat: readImageEditRequestFormat(config),
selectedModelIds: platformModelBaseIds(platform, baseModels, currentModels),
modelDiscountFactors: platformModelDiscountFactors(platform, baseModels, currentModels),
modelNameMappings: platformModelNameMappings(platform, baseModels, currentModels),
@@ -1210,6 +1221,11 @@ function platformToForm(
};
}
function readImageEditRequestFormat(config: Record<string, unknown>): PlatformWizardForm['imageEditRequestFormat'] {
const value = String(config.imageEditRequestFormat ?? '').trim().toLowerCase().replaceAll('-', '_').replaceAll(' ', '_');
return value === 'json' || value === 'json_url' ? 'json_url' : 'multipart';
}
function defaultSelectedModelIds(models: BaseModelCatalogItem[], provider: string) {
return modelsForProvider(models, provider).map((model) => model.id);
}
@@ -26,6 +26,14 @@ function selectedForm() {
}
describe('platform model rate limit payload', () => {
it('uses multipart unless JSON URL image edits are explicitly configured', () => {
const defaultConfig = platformPayload(selectedForm()).config ?? {};
expect(defaultConfig.imageEditRequestFormat).toBeUndefined();
const jsonConfig = platformPayload({ ...selectedForm(), imageEditRequestFormat: 'json_url' }).config ?? {};
expect(jsonConfig.imageEditRequestFormat).toBe('json_url');
});
it('preserves an existing model policy when the edit form was not touched', () => {
const [payload] = platformModelPayloads([baseModel], selectedForm());
@@ -39,6 +39,7 @@ export interface PlatformWizardForm {
httpProxy: string;
supportBase64Input: boolean;
supportUrlInput: boolean;
imageEditRequestFormat: 'multipart' | 'json_url';
modelDiscountFactor: string;
modelDiscountFactors: Record<string, string>;
modelNameMappings: Record<string, string>;
@@ -109,6 +110,7 @@ export function createEmptyPlatformForm(provider = '', defaults?: ProviderConnec
httpProxy: '',
supportBase64Input: true,
supportUrlInput: true,
imageEditRequestFormat: 'multipart',
modelDiscountFactor: '',
modelDiscountFactors: {},
modelNameMappings: {},
@@ -171,6 +173,7 @@ export function platformPayload(form: PlatformWizardForm, options: { preserveEmp
networkProxy: networkProxyPayload(form),
supportBase64Input: form.supportBase64Input,
supportUrlInput: form.supportUrlInput,
...(form.imageEditRequestFormat === 'json_url' ? { imageEditRequestFormat: 'json_url' } : {}),
source: 'gateway-admin',
},
retryPolicy: {