Add multipart image edit support
This commit is contained in:
@@ -35,6 +35,12 @@ type decodedRequestAsset struct {
|
||||
ContentType string
|
||||
}
|
||||
|
||||
type requestAssetOptions struct {
|
||||
RequirePublicURL bool
|
||||
UploadScene string
|
||||
Source string
|
||||
}
|
||||
|
||||
func (s *Server) prepareTaskRequest(ctx context.Context, r *http.Request, user *auth.User, body map[string]any) (preparedTaskRequest, error) {
|
||||
preparedBody, err := s.prepareRequestAssetRefs(ctx, body)
|
||||
if err != nil {
|
||||
@@ -185,6 +191,21 @@ func requestAssetFromValue(key string, path []string, value any, siblings map[st
|
||||
}
|
||||
|
||||
func (s *Server) ensureRequestAsset(ctx context.Context, decoded decodedRequestAsset) (map[string]any, error) {
|
||||
return s.ensureRequestAssetWithOptions(ctx, decoded, requestAssetOptions{
|
||||
UploadScene: store.FileStorageSceneRequestAsset,
|
||||
Source: "ai-gateway-request",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) ensurePublicRequestAsset(ctx context.Context, decoded decodedRequestAsset) (map[string]any, error) {
|
||||
return s.ensureRequestAssetWithOptions(ctx, decoded, requestAssetOptions{
|
||||
RequirePublicURL: true,
|
||||
UploadScene: store.FileStorageSceneUpload,
|
||||
Source: "ai-gateway-form-data",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) ensureRequestAssetWithOptions(ctx context.Context, decoded decodedRequestAsset, options requestAssetOptions) (map[string]any, error) {
|
||||
sum := sha256.Sum256(decoded.Bytes)
|
||||
sha := hex.EncodeToString(sum[:])
|
||||
contentType := strings.TrimSpace(decoded.ContentType)
|
||||
@@ -195,18 +216,29 @@ func (s *Server) ensureRequestAsset(ctx context.Context, decoded decodedRequestA
|
||||
if existing, ok, err := s.store.FindRequestAsset(ctx, sha, contentType); err != nil && !store.IsUndefinedDatabaseObject(err) {
|
||||
return nil, err
|
||||
} else if ok && requestAssetStillUsable(existing, now) {
|
||||
if err := s.store.IncrementRequestAssetRefCount(ctx, sha, contentType); err != nil && !store.IsUndefinedDatabaseObject(err) {
|
||||
return nil, err
|
||||
ref := requestAssetRef(existing)
|
||||
if !options.RequirePublicURL || requestAssetRefHasPublicURL(ref) {
|
||||
if err := s.store.IncrementRequestAssetRefCount(ctx, sha, contentType); err != nil && !store.IsUndefinedDatabaseObject(err) {
|
||||
return nil, err
|
||||
}
|
||||
return ref, nil
|
||||
}
|
||||
return requestAssetRef(existing), nil
|
||||
}
|
||||
|
||||
uploadScene := strings.TrimSpace(options.UploadScene)
|
||||
if uploadScene == "" {
|
||||
uploadScene = store.FileStorageSceneRequestAsset
|
||||
}
|
||||
source := strings.TrimSpace(options.Source)
|
||||
if source == "" {
|
||||
source = "ai-gateway-request"
|
||||
}
|
||||
upload, err := s.runner.UploadFile(ctx, runner.FileUploadPayload{
|
||||
Bytes: decoded.Bytes,
|
||||
ContentType: contentType,
|
||||
FileName: requestAssetFileName(sha, contentType),
|
||||
Scene: store.FileStorageSceneRequestAsset,
|
||||
Source: "ai-gateway-request",
|
||||
Scene: uploadScene,
|
||||
Source: source,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -216,6 +248,9 @@ func (s *Server) ensureRequestAsset(ctx context.Context, decoded decodedRequestA
|
||||
if url == "" {
|
||||
return nil, &clients.ClientError{Code: "request_asset_upload_failed", Message: "file storage response did not include url", Retryable: false}
|
||||
}
|
||||
if options.RequirePublicURL && !requestAssetURLIsPublic(storageProvider, url) {
|
||||
return nil, &clients.ClientError{Code: "request_asset_public_url_required", Message: "multipart image assets require a public file storage URL; enable a non-local file storage channel for uploads", Retryable: false}
|
||||
}
|
||||
var expiresAt *time.Time
|
||||
localPath := ""
|
||||
if storageProvider == "local_static" {
|
||||
@@ -340,6 +375,18 @@ func requestAssetRef(asset store.RequestAsset) map[string]any {
|
||||
}
|
||||
}
|
||||
|
||||
func requestAssetRefHasPublicURL(ref map[string]any) bool {
|
||||
return requestAssetURLIsPublic(stringFromRequestAny(ref["storageProvider"]), stringFromRequestAny(ref["url"]))
|
||||
}
|
||||
|
||||
func requestAssetURLIsPublic(storageProvider string, url string) bool {
|
||||
if strings.EqualFold(strings.TrimSpace(storageProvider), "local_static") {
|
||||
return false
|
||||
}
|
||||
lower := strings.ToLower(strings.TrimSpace(url))
|
||||
return strings.HasPrefix(lower, "http://") || strings.HasPrefix(lower, "https://")
|
||||
}
|
||||
|
||||
func requestAssetStillUsable(asset store.RequestAsset, now time.Time) bool {
|
||||
if asset.ExpiredAt != nil {
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user