Split ingress keys into text/image protocols with Replicate model listing.
CI / docker (push) Successful in 3m38s

Add model input defaults (including prompt), workflow model editing, ingress API docs, and fix img2img image upload filenames for signed URLs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
renjue
2026-06-29 21:41:06 +08:00
parent 2665ac1dee
commit 321683f863
35 changed files with 1646 additions and 167 deletions
+41 -11
View File
@@ -8,7 +8,6 @@ import (
"fmt"
"io"
"net/http"
"path"
"strings"
"sync"
"time"
@@ -67,7 +66,7 @@ func (s *Service) Create(ctx context.Context, ingress *model.IngressKey, req Cre
if !provider.Enabled {
return nil, fmt.Errorf("provider disabled")
}
input, err := ValidateInput(entry.WorkflowType, req.Input)
input, err := ValidateInputWithDefaults(entry.WorkflowType, req.Input, inputDefaultsForEntry(entry))
if err != nil {
return nil, err
}
@@ -85,14 +84,15 @@ func (s *Service) Create(ctx context.Context, ingress *model.IngressKey, req Cre
if err != nil {
return nil, err
}
ingressModelID := entry.IngressID()
inputJSON, _ := json.Marshal(input)
p := model.Prediction{
ID: id,
IngressKeyID: ingress.ID,
ProviderID: provider.ID,
ModelEntryID: entry.ID,
Version: req.Version,
Model: entry.ModelID,
Version: ingressModelID,
Model: ingressModelID,
InputJSON: string(inputJSON),
Status: model.PredictionStarting,
ClientID: "luminary-" + id,
@@ -129,6 +129,14 @@ func (s *Service) Create(ctx context.Context, ingress *model.IngressKey, req Cre
return s.toAPI(&p), nil
}
func inputDefaultsForEntry(entry model.ModelEntry) map[string]any {
defs, err := ParseInputDefaults(entry.InputDefaultsJSON)
if err != nil {
return map[string]any{}
}
return defs
}
func (s *Service) resolveModel(version string, ingress *model.IngressKey) (model.ModelEntry, model.Provider, error) {
allowedProviders := cache.ParseJSONList(ingress.AllowedProvidersJSON)
allowedModels := cache.ParseJSONList(ingress.AllowedModelsJSON)
@@ -145,7 +153,7 @@ func (s *Service) resolveModel(version string, ingress *model.IngressKey) (model
continue
}
ingressID := m.IngressID()
if !m.MatchesIngressName(version) {
if !m.MatchesIngressRef(version, p.Name) {
continue
}
if !cache.MatchesList(allowedModels, ingressID) && !cache.MatchesList(allowedModels, m.ModelID) && !cache.MatchesList(allowedModels, "*") {
@@ -157,6 +165,12 @@ func (s *Service) resolveModel(version string, ingress *model.IngressKey) (model
if len(candidates) == 0 {
return model.ModelEntry{}, model.Provider{}, fmt.Errorf("no model found for version %s", version)
}
if len(candidates) > 1 && !strings.Contains(version, "/") {
return model.ModelEntry{}, model.Provider{}, fmt.Errorf("ambiguous model name %q; use owner/name", version)
}
if len(candidates) > 1 {
return model.ModelEntry{}, model.Provider{}, fmt.Errorf("ambiguous model %q", version)
}
m := candidates[0]
p, ok := s.cache.GetProvider(m.ProviderID)
if !ok {
@@ -180,15 +194,11 @@ func (s *Service) execute(ctx context.Context, p *model.Prediction, entry model.
runInput := cloneMap(input)
if entry.WorkflowType == model.WorkflowImg2Img {
imageURL, _ := runInput["image"].(string)
data, err := downloadURL(ctx, imageURL)
data, name, err := s.loadInputImage(ctx, imageURL)
if err != nil {
s.fail(p, clientIP, start, fmt.Errorf("download image: %w", err))
return
}
name := path.Base(imageURL)
if name == "" || name == "." || name == "/" {
name = "input.png"
}
uploaded, err := client.UploadImage(ctx, data, name)
if err != nil {
s.fail(p, clientIP, start, fmt.Errorf("upload image: %w", err))
@@ -409,6 +419,26 @@ func formatTime(t *time.Time) any {
return t.UTC().Format(time.RFC3339Nano)
}
func (s *Service) loadInputImage(ctx context.Context, imageURL string) ([]byte, string, error) {
if id, ok := parseGatewayFileID(imageURL); ok && s.images != nil {
data, img, err := s.images.ReadContent(id)
if err == nil {
if err := validateImageBytes(data); err != nil {
return nil, "", err
}
return data, imageFilenameFromStored(img.Filename, img.Mime, data), nil
}
}
data, err := downloadURL(ctx, imageURL)
if err != nil {
return nil, "", err
}
if err := validateImageBytes(data); err != nil {
return nil, "", err
}
return data, imageFilenameFromURL(imageURL, data), nil
}
func downloadURL(ctx context.Context, raw string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, raw, nil)
if err != nil {
@@ -455,7 +485,7 @@ func (s *Service) recordUsage(ingress *model.IngressKey, provider *model.Provide
ProviderID: providerID,
ClientIP: clientIP,
Endpoint: model.EndpointPrediction,
Model: p.ID,
Model: p.Model,
LatencyMs: time.Since(start).Milliseconds(),
Status: status,
ErrorMsg: errMsg,