feat: sync deployed v1.0.0-rc.38-custom.42
Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
This commit is contained in:
1 parent
aa6c3f50f4
commit
ceddfd3d6b
828 files changed
+122253
-13702
No files matched your search
@@ -0,0 +1,146 @@
|
||||
package jsplugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"mime"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"github.com/QuantumNous/new-api/constant"
|
||||
pluginruntime "github.com/QuantumNous/new-api/pkg/jsplugin"
|
||||
"github.com/QuantumNous/new-api/relay/helper"
|
||||
)
|
||||
|
||||
// readSubmitEvents owns only SSE framing and bounded JSON state. The plugin
|
||||
// interprets data, accumulates provider output and decides when it is complete.
|
||||
// No response bytes are written to the client at this boundary.
|
||||
func (a *TaskAdaptor) readSubmitEvents(parent context.Context, resp *http.Response, driverContext map[string]any) (any, error) {
|
||||
mediaType, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
||||
if err != nil || mediaType != "text/event-stream" {
|
||||
return nil, fmt.Errorf("expected a text/event-stream submit response")
|
||||
}
|
||||
ctx, cancel := context.WithCancelCause(parent)
|
||||
defer cancel(nil)
|
||||
stopClose := context.AfterFunc(ctx, func() { _ = resp.Body.Close() })
|
||||
defer stopClose()
|
||||
idleTimeout := time.Duration(constant.StreamingTimeout) * time.Second
|
||||
if idleTimeout <= 0 {
|
||||
idleTimeout = 30 * time.Second
|
||||
}
|
||||
timer := time.AfterFunc(idleTimeout, func() { cancel(fmt.Errorf("task submit stream idle timeout")) })
|
||||
defer timer.Stop()
|
||||
|
||||
scanner := helper.NewStreamScanner(resp.Body, maxTaskPluginPersistedJSONBytes+1)
|
||||
hook := "parseSubmitEvent"
|
||||
var accumulated *pluginruntime.JSONState
|
||||
if slices.Contains(a.plugin.Meta.RequiredCapabilities, pluginruntime.CapabilitySubmitSSEDelta) {
|
||||
hook = "parseSubmitEventDelta"
|
||||
accumulated = pluginruntime.NewJSONState(maxTaskPluginPersistedJSONBytes)
|
||||
}
|
||||
var state any
|
||||
var data []string
|
||||
var eventName, lastID string
|
||||
frameBytes := 0
|
||||
firstLine := true
|
||||
for scanner.Scan() {
|
||||
if err := context.Cause(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
timer.Reset(idleTimeout)
|
||||
line := scanner.Text()
|
||||
if firstLine {
|
||||
line = strings.TrimPrefix(line, "\ufeff")
|
||||
firstLine = false
|
||||
}
|
||||
frameBytes += len(line) + 1
|
||||
if frameBytes > maxTaskPluginPersistedJSONBytes {
|
||||
return nil, fmt.Errorf("task submit SSE event exceeds size limit")
|
||||
}
|
||||
if line != "" {
|
||||
field, value, _ := strings.Cut(line, ":")
|
||||
value, _ = strings.CutPrefix(value, " ")
|
||||
switch field {
|
||||
case "data":
|
||||
data = append(data, value)
|
||||
case "event":
|
||||
eventName = value
|
||||
case "id":
|
||||
if !strings.ContainsRune(value, '\x00') {
|
||||
lastID = value
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
frameBytes = 0
|
||||
if len(data) == 0 {
|
||||
eventName = ""
|
||||
continue
|
||||
}
|
||||
if eventName == "" {
|
||||
eventName = "message"
|
||||
}
|
||||
event := map[string]any{"event": eventName, "id": lastID, "data": strings.Join(data, "\n")}
|
||||
value, err := a.plugin.Engine.Call(ctx, hook, driverContext, event, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, ok := value.(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s must return an object", hook)
|
||||
}
|
||||
nextState, hasState := result["state"]
|
||||
done, hasDone := result["done"].(bool)
|
||||
if !hasState || !hasDone {
|
||||
return nil, fmt.Errorf("%s must return state and a boolean done", hook)
|
||||
}
|
||||
encoded, err := common.Marshal(nextState)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid submit stream state: %w", err)
|
||||
}
|
||||
if len(encoded) > maxTaskPluginPersistedJSONBytes {
|
||||
return nil, fmt.Errorf("task submit stream state exceeds size limit")
|
||||
}
|
||||
if accumulated != nil {
|
||||
if len(result) != 3 {
|
||||
return nil, fmt.Errorf("parseSubmitEventDelta must return only changes, state and done")
|
||||
}
|
||||
// Control state is separate from the full result and is the only
|
||||
// snapshot passed back to JS on the next event.
|
||||
if len(encoded) > 64<<10 {
|
||||
return nil, fmt.Errorf("task submit stream control state exceeds size limit")
|
||||
}
|
||||
if err = accumulated.Apply(ctx, result["changes"]); err != nil {
|
||||
return nil, fmt.Errorf("invalid submit stream changes: %w", err)
|
||||
}
|
||||
}
|
||||
// Keep the exact encoded-byte limit above. Plain JSON state can be
|
||||
// isolated without parsing large strings again; codec-specific values
|
||||
// (for example exported typed arrays) retain the old normalization.
|
||||
var plainJSON bool
|
||||
state, plainJSON = cloneJSONValue(nextState, 0)
|
||||
if !plainJSON {
|
||||
if err = common.Unmarshal(encoded, &state); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if done {
|
||||
if accumulated != nil {
|
||||
return accumulated.Value()
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
data = nil
|
||||
eventName = ""
|
||||
}
|
||||
if err := context.Cause(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("read task submit stream: %w", err)
|
||||
}
|
||||
return nil, fmt.Errorf("task submit stream ended before the plugin reported completion")
|
||||
}
|
||||
Reference in new issue
Block a user