Allow emergency model provider disable
This commit is contained in:
@@ -8,11 +8,11 @@ type Dependencies={queryRows(sql:string,values?:readonly unknown[]):Promise<read
|
||||
const json=(body:unknown,status=200)=>Response.json(body,{status});
|
||||
const immutable=(e:unknown)=>e instanceof Error&&e.message.includes("model_provider_runtime_immutable");
|
||||
async function versionProvider(d:Dependencies,where:string,values:readonly unknown[]){const rows=await d.queryRows(`select p.id,p.code,p.provider_type,p.base_url,p.encrypted_api_key,p.enabled,v.id version_id,v.enabled version_enabled from public.model_config_versions v join public.model_providers p on p.id=v.provider_id where ${where}`,values);return rows[0] as ProviderRow|undefined;}
|
||||
async function probeAndRecord(p:ProviderRow,actor:string,rid:string,d:Dependencies){if(!p.enabled||!p.version_enabled||!p.encrypted_api_key)return{recorded:false,status:0,success:false};let key="";try{key=decryptModelProviderApiKey({encryptedApiKey:p.encrypted_api_key},d.environment);}catch{return{recorded:false,status:0,success:false};}if(p.provider_type==="openai-compatible")await d.assertAllowedUrl(p.base_url??"");let status=0;try{status=await d.probeAllowed(modelProviderModelsUrl({providerType:p.provider_type,baseUrl:p.base_url}),modelProviderRequestHeaders(p.provider_type,key));}catch{}await d.queryRows("select public.admin_record_model_connection_test($1,$2,$3,$4) id",[actor,p.version_id,status,rid]);return{recorded:true,status,success:modelConnectionTestSucceeded(status)};}
|
||||
async function probeAndRecord(p:ProviderRow,actor:string,rid:string,d:Dependencies,allowDisabled=false){if((!allowDisabled&&(!p.enabled||!p.version_enabled))||!p.encrypted_api_key)return{recorded:false,status:0,success:false};let key="";try{key=decryptModelProviderApiKey({encryptedApiKey:p.encrypted_api_key},d.environment);}catch{return{recorded:false,status:0,success:false};}if(p.provider_type==="openai-compatible")await d.assertAllowedUrl(p.base_url??"");let status=0;try{status=await d.probeAllowed(modelProviderModelsUrl({providerType:p.provider_type,baseUrl:p.base_url}),modelProviderRequestHeaders(p.provider_type,key));}catch{}await d.queryRows("select public.admin_record_model_connection_test($1,$2,$3,$4) id",[actor,p.version_id,status,rid]);return{recorded:true,status,success:modelConnectionTestSucceeded(status)};}
|
||||
export async function handleAdminModelMutation(a:AdminModelMutation,c:Readonly<{actorUserId:string;requestId:string}>,d:Dependencies){
|
||||
if(a.action==="saveProvider"){if(a.providerType==="openai-compatible")await d.assertAllowedUrl(a.baseUrl??"");const key=a.apiKey?.trim();let encrypted:string|null=null;if(key)encrypted=encryptModelProviderApiKey(key,d.environment);else if(!a.id&&a.enabled)return json({error:"模型供应商密钥未配置"},409);try{const rows=await d.queryRows("select public.admin_save_model_provider($1,$2,$3,$4,$5,$6,$7,$8,$9) id",[c.actorUserId,a.id??null,a.name,a.providerType,a.providerType==="openai-compatible"?a.baseUrl??null:null,encrypted,a.enabled,a.reason,c.requestId]);return json({data:{id:rows[0]!.id,requestId:c.requestId}});}catch(e){if(immutable(e))return json({error:"已发布或已退役版本使用的供应商连接配置不可修改,请新建供应商和模型版本后重新测试并发布",code:"model_provider_runtime_immutable"},409);if(e instanceof Error&&e.message.includes("model_provider_code_immutable"))return json({error:"供应商代码不可修改",code:"model_provider_code_immutable"},409);throw e;}}
|
||||
if(a.action==="saveProvider"){if(a.providerType==="openai-compatible")await d.assertAllowedUrl(a.baseUrl??"");const key=a.apiKey?.trim();let encrypted:string|null=null;if(key)encrypted=encryptModelProviderApiKey(key,d.environment);else if(!a.id&&a.enabled)return json({error:"模型供应商密钥未配置"},409);try{const rows=await d.queryRows("select public.admin_save_model_provider($1,$2,$3,$4,$5,$6,$7,$8,$9) id",[c.actorUserId,a.id??null,a.name,a.providerType,a.providerType==="openai-compatible"?a.baseUrl??null:null,encrypted,a.enabled,a.reason,c.requestId]);d.invalidateCatalog();return json({data:{id:rows[0]!.id,requestId:c.requestId}});}catch(e){if(immutable(e))return json({error:"已发布或已退役版本使用的供应商连接配置不可修改,请新建供应商和模型版本后重新测试并发布",code:"model_provider_runtime_immutable"},409);if(e instanceof Error&&e.message.includes("model_provider_code_immutable"))return json({error:"供应商代码不可修改",code:"model_provider_code_immutable"},409);if(e instanceof Error&&e.message.includes("model_provider_reenable_test_required"))return json({error:"重新启用前,请先在模型版本列表中测试此供应商的已发布或已退役版本",code:"model_provider_reenable_test_required"},409);throw e;}}
|
||||
if(a.action==="saveDraft"){const rows=await d.queryRows("select public.admin_save_model_draft($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16::jsonb,$17,$18) id",[c.actorUserId,a.modelId,a.versionId??null,a.providerId,a.label,a.description,a.providerModel,a.modelTier,a.creditCost,a.contextWindow??null,a.inputCostMicrousdPerMillion,a.outputCostMicrousdPerMillion,a.enabled,a.isDefault,a.fallbackModelId??null,JSON.stringify(a.settings),a.reason,c.requestId]);return json({data:{id:rows[0]!.id,requestId:c.requestId}});}
|
||||
if(a.action==="test"){const p=await versionProvider(d,"v.id=$1 and v.status in ('draft','published','retired')",[a.versionId]);if(!p)return json({error:"模型版本不存在"},404);const r=await probeAndRecord(p,c.actorUserId,c.requestId,d);if(!r.recorded)return json({error:"模型版本或供应商未启用,或数据库密钥不可用"},409);return json({data:{id:p.version_id,reachable:r.success,status:r.status,secretConfigured:true,requestId:c.requestId}},r.success?200:409);}
|
||||
if(a.action==="test"){const p=await versionProvider(d,"v.id=$1 and v.status in ('draft','published','retired')",[a.versionId]);if(!p)return json({error:"模型版本不存在"},404);const r=await probeAndRecord(p,c.actorUserId,c.requestId,d,true);if(!r.recorded)return json({error:"数据库密钥不可用"},409);return json({data:{id:p.version_id,reachable:r.success,status:r.status,secretConfigured:true,requestId:c.requestId}},r.success?200:409);}
|
||||
if(a.action==="publish"){const rows=await d.queryRows("select public.admin_publish_model($1,$2,$3,$4) id",[c.actorUserId,a.versionId,a.reason,c.requestId]);d.invalidateCatalog();return json({data:{id:rows[0]!.id,requestId:c.requestId}});}
|
||||
const p=await versionProvider(d,"v.config_id=$1 and v.version=$2 and v.status='retired'",[a.configId,a.targetVersion]);if(!p)return json({error:"回滚版本不存在"},404);const r=await probeAndRecord(p,c.actorUserId,c.requestId,d);if(!r.recorded)return json({error:"回滚版本或供应商未启用,或数据库密钥不可用"},409);if(!r.success)return json({error:"回滚版本运行时连接测试失败",data:{status:r.status,requestId:c.requestId}},409);const rows=await d.queryRows("select public.admin_rollback_model($1,$2,$3,$4,$5) id",[c.actorUserId,a.configId,a.targetVersion,a.reason,c.requestId]);d.invalidateCatalog();return json({data:{id:rows[0]!.id,requestId:c.requestId}});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user