package platform import ( "context" "crypto/sha256" "database/sql" "encoding/hex" "errors" "gitea.entroia.com/Zayden-Jung/foundry-core/internal/codex" ) func (s *service) persistCanvasImage(ctx context.Context, tx *sql.Tx, run canvasQueueRun, outputID string, image *codex.CanvasImage) (response, error) { if image == nil || len(image.Data) == 0 || len(image.Data) > assetLimit { return nil, errors.New("invalid_image_output") } mime, width, height, err := inspectAsset(image.Data) if err != nil || mime != image.MIME { return nil, errors.New("invalid_image_output") } digest := sha256.Sum256(image.Data) var id string err = tx.QueryRowContext(ctx, `INSERT INTO canvas_assets(project,workspace_id,uploaded_by,mime_type,bytes,width,height,digest,idempotency_key) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING id::text`, run.Project, run.Workspace, run.Account, mime, image.Data, width, height, hex.EncodeToString(digest[:]), outputID).Scan(&id) if err != nil { return nil, err } // Conflict outputs remain readable under the run's originating material even // before explicit application. No transient or worker-local URL is persisted. _, err = tx.ExecContext(ctx, `INSERT INTO canvas_asset_bindings(project,workspace_id,asset_id,board_id) VALUES($1,$2,$3,$4)`, run.Project, run.Workspace, id, run.Board) if err != nil { return nil, err } return response{"output_id": outputID, "kind": "image", "assetId": id, "src": s.assetURL(run.Project, run.Workspace, id), "mime_type": mime, "bytes": len(image.Data), "width": width, "height": height}, nil } func canvasModelSupports(model codex.CanvasModel, modality string) bool { for _, value := range model.OutputModalities { if value == modality { return true } } return false } func canvasModelAccepts(model codex.CanvasModel, modality string) bool { for _, value := range model.InputModalities { if value == modality { return true } } return false }