feat: publish v1.0.0-rc.36-custom.1
Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 9m20s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 55m7s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s
Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 9m20s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 55m7s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s
This commit is contained in:
commit
bb7968b612
2381 files changed
+551494
No files matched your search
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,45 @@
|
||||
package jsplugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
pluginruntime "github.com/QuantumNous/new-api/pkg/jsplugin"
|
||||
vertexcore "github.com/QuantumNous/new-api/relay/channel/vertex"
|
||||
)
|
||||
|
||||
type cachedAuth struct {
|
||||
header, projectID string
|
||||
expiresAt time.Time
|
||||
}
|
||||
|
||||
var pluginAuthCache sync.Map
|
||||
var acquireAccessToken = vertexcore.AcquireAccessToken
|
||||
|
||||
func resolveAuth(meta pluginruntime.AuthMeta, apiKey, proxy string) (map[string]any, error) {
|
||||
typeName := strings.TrimSpace(meta.Type)
|
||||
if typeName == "" || typeName == "none" || typeName == "api_key" {
|
||||
return map[string]any{"authHeader": apiKey}, nil
|
||||
}
|
||||
cacheKey := apiKey + "\x00" + proxy
|
||||
if value, ok := pluginAuthCache.Load(cacheKey); ok {
|
||||
entry := value.(cachedAuth)
|
||||
if time.Now().Before(entry.expiresAt) {
|
||||
return map[string]any{"authHeader": entry.header, "projectId": entry.projectID}, nil
|
||||
}
|
||||
}
|
||||
var credentials vertexcore.Credentials
|
||||
if err := common.Unmarshal([]byte(apiKey), &credentials); err != nil {
|
||||
return nil, fmt.Errorf("decode oauth2_jwt credentials: %w", err)
|
||||
}
|
||||
token, err := acquireAccessToken(credentials, proxy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entry := cachedAuth{header: "Bearer " + token, projectID: credentials.ProjectID, expiresAt: time.Now().Add(25 * time.Minute)}
|
||||
pluginAuthCache.Store(cacheKey, entry)
|
||||
return map[string]any{"authHeader": entry.header, "projectId": entry.projectID}, nil
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package jsplugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
pluginruntime "github.com/QuantumNous/new-api/pkg/jsplugin"
|
||||
vertexcore "github.com/QuantumNous/new-api/relay/channel/vertex"
|
||||
relaycommon "github.com/QuantumNous/new-api/relay/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOAuth2JWTAuthCachesAndRefreshes(t *testing.T) {
|
||||
pluginAuthCache = sync.Map{}
|
||||
original := acquireAccessToken
|
||||
t.Cleanup(func() { acquireAccessToken = original; pluginAuthCache = sync.Map{} })
|
||||
calls := 0
|
||||
acquireAccessToken = func(_ vertexcore.Credentials, _ string) (string, error) {
|
||||
calls++
|
||||
return fmt.Sprintf("token-%d", calls), nil
|
||||
}
|
||||
credentials, err := common.Marshal(vertexcore.Credentials{ProjectID: "project", ClientEmail: "a@example.com", PrivateKey: "secret"})
|
||||
require.NoError(t, err)
|
||||
meta := pluginruntime.AuthMeta{Type: "oauth2_jwt"}
|
||||
first, err := resolveAuth(meta, string(credentials), "")
|
||||
require.NoError(t, err)
|
||||
second, err := resolveAuth(meta, string(credentials), "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Bearer token-1", first["authHeader"])
|
||||
assert.Equal(t, first, second)
|
||||
assert.Equal(t, 1, calls)
|
||||
pluginAuthCache.Store(string(credentials)+"\x00", cachedAuth{expiresAt: time.Now().Add(-time.Second)})
|
||||
refreshed, err := resolveAuth(meta, string(credentials), "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Bearer token-2", refreshed["authHeader"])
|
||||
assert.Equal(t, 2, calls)
|
||||
}
|
||||
|
||||
func TestOAuth2JWTContextDoesNotExposeServiceAccountKey(t *testing.T) {
|
||||
pluginAuthCache = sync.Map{}
|
||||
original := acquireAccessToken
|
||||
t.Cleanup(func() { acquireAccessToken = original; pluginAuthCache = sync.Map{} })
|
||||
acquireAccessToken = func(_ vertexcore.Credentials, _ string) (string, error) {
|
||||
return "access-token", nil
|
||||
}
|
||||
credentials, err := common.Marshal(vertexcore.Credentials{ProjectID: "project", ClientEmail: "a@example.com", PrivateKey: "secret"})
|
||||
require.NoError(t, err)
|
||||
source := `
|
||||
export const meta = {apiVersion:1,key:"oauth",name:"OAuth",version:"1.0.0",author:{name:"Test"},models:["m"],fetchMode:"per_task",auth:{type:"oauth2_jwt"}};
|
||||
export function buildSubmitRequest(ctx) {
|
||||
if (ctx.apiKey !== undefined) throw new Error("raw key exposed");
|
||||
return {url:ctx.baseUrl+"/submit",headers:{Authorization:ctx.authHeader}};
|
||||
}
|
||||
export function parseSubmitResponse(){return {taskId:"1"}}
|
||||
export function buildQueryRequest(){return {url:"https://example.com"}}
|
||||
export function parseTaskResult(){return {status:"SUCCESS"}}
|
||||
`
|
||||
plugin, err := pluginruntime.NewRegistry().Register(source, pluginruntime.Options{})
|
||||
require.NoError(t, err)
|
||||
adaptor := New(plugin)
|
||||
info := &relaycommon.RelayInfo{ChannelMeta: &relaycommon.ChannelMeta{ChannelBaseUrl: "https://provider.example", ApiKey: string(credentials)}, TaskRelayInfo: &relaycommon.TaskRelayInfo{}}
|
||||
adaptor.Init(info)
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c.Request = httptest.NewRequest(http.MethodPost, "/v1/videos", nil)
|
||||
c.Set("task_request", relaycommon.TaskSubmitReq{Prompt: "p"})
|
||||
require.Nil(t, adaptor.ValidateRequestAndSetAction(c, info))
|
||||
req := httptest.NewRequest(http.MethodPost, "https://provider.example/submit", nil)
|
||||
require.NoError(t, adaptor.BuildRequestHeader(c, req, info))
|
||||
assert.Equal(t, "Bearer access-token", req.Header.Get("Authorization"))
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package taskcommon
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"github.com/QuantumNous/new-api/model"
|
||||
relaycommon "github.com/QuantumNous/new-api/relay/common"
|
||||
"github.com/QuantumNous/new-api/setting/system_setting"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// UnmarshalMetadata converts a map[string]any metadata to a typed struct via JSON round-trip.
|
||||
// This replaces the repeated pattern: json.Marshal(metadata) → json.Unmarshal(bytes, &target).
|
||||
func UnmarshalMetadata(metadata map[string]any, target any) error {
|
||||
if metadata == nil {
|
||||
return nil
|
||||
}
|
||||
// Prevent metadata from overriding model fields to avoid billing bypass.
|
||||
delete(metadata, "model")
|
||||
metaBytes, err := common.Marshal(metadata)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal metadata failed: %w", err)
|
||||
}
|
||||
if err := common.Unmarshal(metaBytes, target); err != nil {
|
||||
return fmt.Errorf("unmarshal metadata failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultString returns val if non-empty, otherwise fallback.
|
||||
func DefaultString(val, fallback string) string {
|
||||
if val == "" {
|
||||
return fallback
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// DefaultInt returns val if non-zero, otherwise fallback.
|
||||
func DefaultInt(val, fallback int) int {
|
||||
if val == 0 {
|
||||
return fallback
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// EncodeLocalTaskID encodes an upstream operation name to a URL-safe base64 string.
|
||||
// Used by Gemini/Vertex to store upstream names as task IDs.
|
||||
func EncodeLocalTaskID(name string) string {
|
||||
return base64.RawURLEncoding.EncodeToString([]byte(name))
|
||||
}
|
||||
|
||||
// DecodeLocalTaskID decodes a base64-encoded upstream operation name.
|
||||
func DecodeLocalTaskID(id string) (string, error) {
|
||||
b, err := base64.RawURLEncoding.DecodeString(id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
// BuildProxyURL constructs the video proxy URL using the public task ID.
|
||||
// e.g., "https://your-server.com/v1/videos/task_xxxx/content"
|
||||
func BuildProxyURL(taskID string) string {
|
||||
return fmt.Sprintf("%s/v1/videos/%s/content", system_setting.ServerAddress, taskID)
|
||||
}
|
||||
|
||||
// Status-to-progress mapping constants for polling updates.
|
||||
const (
|
||||
ProgressSubmitted = "10%"
|
||||
ProgressQueued = "20%"
|
||||
ProgressInProgress = "30%"
|
||||
ProgressComplete = "100%"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BaseBilling — embeddable no-op implementations for TaskAdaptor billing methods.
|
||||
// Adaptors that do not need custom billing can embed this struct directly.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type BaseBilling struct{}
|
||||
|
||||
// EstimateBilling returns nil (no extra ratios; use base model price).
|
||||
func (BaseBilling) EstimateBilling(_ *gin.Context, _ *relaycommon.RelayInfo) map[string]float64 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AdjustBillingOnSubmit returns nil (no submit-time adjustment).
|
||||
func (BaseBilling) AdjustBillingOnSubmit(_ *relaycommon.RelayInfo, _ []byte) map[string]float64 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AdjustBillingOnComplete returns 0 (keep pre-charged amount).
|
||||
func (BaseBilling) AdjustBillingOnComplete(_ *model.Task, _ *relaycommon.TaskInfo) int {
|
||||
return 0
|
||||
}
|
||||
Reference in new issue
Block a user