feat(task): replace built-in task adaptors with a sandboxed JS plugin system (#7076)

This commit is contained in:
Calcium-Ion
2026-08-29 18:51:57 +08:00
committed by GitHub
parent 7037ac15bd
commit eb48396d5f
336 changed files with 52333 additions and 6369 deletions
+44 -5
View File
@@ -13,6 +13,7 @@ import (
"github.com/QuantumNous/new-api/constant"
"github.com/QuantumNous/new-api/i18n"
"github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/pkg/jsplugin"
relaychannel "github.com/QuantumNous/new-api/relay/channel"
"github.com/QuantumNous/new-api/relay/channel/ollama"
relaycommon "github.com/QuantumNous/new-api/relay/common"
@@ -480,6 +481,21 @@ func validateChannel(channel *model.Channel, isAdd bool) error {
if err := channel.ValidateSettings(); err != nil {
return fmt.Errorf("渠道额外设置[channel setting] 格式错误:%s", err.Error())
}
if channel.Type == constant.ChannelTypeTaskPlugin {
pluginKey := strings.TrimSpace(channel.GetSetting().TaskPluginKey)
if pluginKey == "" {
return fmt.Errorf("task plugin key is required")
}
if len(pluginKey) > 30 {
return fmt.Errorf("task plugin key must not exceed 30 characters")
}
if _, ok := jsplugin.DefaultRegistry.Get(pluginKey); !ok {
return fmt.Errorf("task plugin %q is not registered", pluginKey)
}
if channel.BaseURL == nil || strings.TrimSpace(*channel.BaseURL) == "" {
return fmt.Errorf("base URL is required for task plugin channels")
}
}
if channel.Type == constant.ChannelTypeNewAPI && strings.TrimSpace(channel.GetBaseURL()) == "" {
return fmt.Errorf("New API channel base URL cannot be empty")
@@ -617,6 +633,15 @@ func AddChannel(c *gin.Context) {
return
}
if addChannelRequest.Channel != nil && addChannelRequest.Channel.Type == constant.ChannelTypeTaskPlugin &&
!authz.Can(c.GetInt("id"), c.GetInt("role"), authz.TaskPluginBind) {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "task plugin channels require the task_plugin.bind permission",
})
return
}
// 使用统一的校验函数
if err := validateChannel(addChannelRequest.Channel, true); err != nil {
c.JSON(http.StatusOK, gin.H{
@@ -964,6 +989,15 @@ func UpdateChannel(c *gin.Context) {
}
clearChannelReadOnlyFields(&channel, requestData)
if channel.Type == constant.ChannelTypeTaskPlugin &&
!authz.Can(c.GetInt("id"), c.GetInt("role"), authz.TaskPluginBind) {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "task plugin channels require the task_plugin.bind permission",
})
return
}
// 使用统一的校验函数
if err := validateChannel(&channel.Channel, false); err != nil {
c.JSON(http.StatusOK, gin.H{
@@ -1299,7 +1333,7 @@ func FetchModels(c *gin.Context) {
baseURL = strings.TrimSpace(*req.BaseURL)
}
if baseURL == "" {
baseURL = constant.ChannelBaseURLs[req.Type]
baseURL = constant.GetChannelBaseURL(req.Type)
}
key := strings.TrimSpace(req.Key)
@@ -1424,6 +1458,11 @@ func CopyChannel(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "获取渠道信息失败,请稍后重试"})
return
}
if origin.Type == constant.ChannelTypeTaskPlugin &&
!authz.Can(c.GetInt("id"), c.GetInt("role"), authz.TaskPluginBind) {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "task plugin channels require the task_plugin.bind permission"})
return
}
// clone channel
clone := *origin // shallow copy is sufficient as we will overwrite primitives
@@ -2010,7 +2049,7 @@ func OllamaPullModel(c *gin.Context) {
return
}
baseURL := constant.ChannelBaseURLs[channel.Type]
baseURL := constant.GetChannelBaseURL(channel.Type)
if channel.GetBaseURL() != "" {
baseURL = channel.GetBaseURL()
}
@@ -2073,7 +2112,7 @@ func OllamaPullModelStream(c *gin.Context) {
return
}
baseURL := constant.ChannelBaseURLs[channel.Type]
baseURL := constant.GetChannelBaseURL(channel.Type)
if channel.GetBaseURL() != "" {
baseURL = channel.GetBaseURL()
}
@@ -2155,7 +2194,7 @@ func OllamaDeleteModel(c *gin.Context) {
return
}
baseURL := constant.ChannelBaseURLs[channel.Type]
baseURL := constant.GetChannelBaseURL(channel.Type)
if channel.GetBaseURL() != "" {
baseURL = channel.GetBaseURL()
}
@@ -2204,7 +2243,7 @@ func OllamaVersion(c *gin.Context) {
return
}
baseURL := constant.ChannelBaseURLs[channel.Type]
baseURL := constant.GetChannelBaseURL(channel.Type)
if channel.GetBaseURL() != "" {
baseURL = channel.GetBaseURL()
}